diff --git a/CMakeLists.txt b/CMakeLists.txt index 3147b5d035..a154ae6c34 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,108 +1,108 @@ # KDevelop version set( KDEVELOP_VERSION_MAJOR 4 ) set( KDEVELOP_VERSION_MINOR 90 ) set( KDEVELOP_VERSION_PATCH 90 ) set( KDEVELOP_VERSION "${KDEVELOP_VERSION_MAJOR}.${KDEVELOP_VERSION_MINOR}.${KDEVELOP_VERSION_PATCH}" ) ################################################################################ cmake_minimum_required(VERSION 2.8.12) project(KDevelop) # we need some parts of the ECM CMake helpers find_package (ECM 0.0.9 REQUIRED NO_MODULE) set(CMAKE_MODULE_PATH ${KDevelop_SOURCE_DIR}/cmake/modules ${ECM_MODULE_PATH}) include(ECMOptionalAddSubdirectory) include(ECMInstallIcons) include(ECMSetupVersion) include(ECMAddTests) include(ECMMarkNonGuiExecutable) include(ECMGenerateHeaders) include(GenerateExportHeader) include(CMakePackageConfigHelpers) include(FeatureSummary) include(WriteBasicConfigVersionFile) include(CheckFunctionExists) include(KDEInstallDirs) include(KDECMakeSettings) include(KDECompilerSettings) set(QT_MIN_VERSION "5.4.0") find_package(Qt5 ${QT_MIN_VERSION} CONFIG REQUIRED Widgets Concurrent Quick QuickWidgets WebKitWidgets Script Test) set(KF5_DEP_VERSION "5.3.0") find_package(KF5 ${KF5_DEP_VERSION} REQUIRED COMPONENTS Config Declarative DocTools IconThemes I18n ItemModels ItemViews JobWidgets KCMUtils KIO NewStuff NotifyConfig Parts Service TextEditor ThreadWeaver XmlGui WindowSystem ) -find_package(KDevelop-PG-Qt) +find_package(KDevelop-PG-Qt 1.9.90) set_package_properties(KDevelop-PG-Qt PROPERTIES PURPOSE "KDevelop parser generator library. Required for the QMake Builder/Manager plugin." ) find_package(KDevPlatform ${KDEVELOP_VERSION} REQUIRED) add_definitions( -DQT_DEPRECATED_WARNINGS -DQT_DISABLE_DEPRECATED_BEFORE=0x050400 -DQT_NO_URL_CAST_FROM_STRING -DQT_STRICT_ITERATORS -DQT_USE_FAST_CONCATENATION -DQT_USE_FAST_OPERATOR_PLUS ) # Turn off missing-field-initializers warning to avoid noise from false positives with empty {} # See discussion: http://mail.kde.org/pipermail/kdevelop-devel/2014-February/046910.html if (CMAKE_COMPILER_IS_GNUCXX) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-missing-field-initializers") endif() include_directories(${KDevelop_SOURCE_DIR} ${KDevelop_BINARY_DIR} ) # create config.h configure_file (config.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config.h ) add_subdirectory(cmake) add_subdirectory(pics) add_subdirectory(app) add_subdirectory(formatters) add_subdirectory(languages) add_subdirectory(projectbuilders) add_subdirectory(projectmanagers) add_subdirectory(debuggers) add_subdirectory(app_templates) add_subdirectory(documentation) add_subdirectory(kdeintegration) add_subdirectory(utils) add_subdirectory(file_templates) add_subdirectory(providers) add_subdirectory(doc) include(CTest) # CTestCustom.cmake has to be in the CTEST_BINARY_DIR. # in the KDE build system, this is the same as CMAKE_BINARY_DIR. configure_file(${CMAKE_SOURCE_DIR}/CTestCustom.cmake ${CMAKE_BINARY_DIR}/CTestCustom.cmake) install(FILES kdevelop.appdata.xml DESTINATION ${SHARE_INSTALL_PREFIX}/appdata/) feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/languages/CMakeLists.txt b/languages/CMakeLists.txt index 22d29ec522..303ef60559 100644 --- a/languages/CMakeLists.txt +++ b/languages/CMakeLists.txt @@ -1,10 +1,12 @@ ecm_optional_add_subdirectory(plugins) ecm_optional_add_subdirectory(qmljs) -option(LEGACY_CPP_SUPPORT "Enable legacy C++ backend" OFF) +# TODO: For now, enable legacy support on Windows (kdev-clang is not compilable on latest MSVC) +# Enable clang by default on all other platforms +option(LEGACY_CPP_SUPPORT "Enable legacy C++ backend" ${WIN32}) if(LEGACY_CPP_SUPPORT) ecm_optional_add_subdirectory(cpp) else() ecm_optional_add_subdirectory(clang) endif() diff --git a/languages/clang/clangparsejob.cpp b/languages/clang/clangparsejob.cpp index 48afb5fe1b..b408567f28 100644 --- a/languages/clang/clangparsejob.cpp +++ b/languages/clang/clangparsejob.cpp @@ -1,362 +1,369 @@ /* This file is part of KDevelop Copyright 2013 Olivier de Gaalon Copyright 2013 Milian Wolff This library 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 library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "clangparsejob.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "clangsettings/clangsettingsmanager.h" #include "duchain/clanghelpers.h" #include "duchain/clangpch.h" #include "duchain/duchainutils.h" #include "duchain/parsesession.h" #include "duchain/clangindex.h" #include "duchain/clangparsingenvironmentfile.h" #include "util/clangdebug.h" #include "util/clangtypes.h" #include "clangsupport.h" #include "documentfinderhelpers.h" #include #include #include #include #include #include using namespace KDevelop; namespace { QString findConfigFile(const QString& forFile, const QString& configFileName) { QDir dir = QFileInfo(forFile).dir(); while (dir.exists()) { const QFileInfo customIncludePaths(dir, configFileName); if (customIncludePaths.exists()) { return customIncludePaths.absoluteFilePath(); } if (!dir.cdUp()) { break; } } return {}; } Path::List readPathListFile(const QString& filepath) { if (filepath.isEmpty()) { return {}; } QFile f(filepath); if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) { return {}; } const QString text = QString::fromLocal8Bit(f.readAll()); const QStringList lines = text.split(QLatin1Char('\n'), QString::SkipEmptyParts); Path::List paths(lines.length()); std::transform(lines.begin(), lines.end(), paths.begin(), [] (const QString& line) { return Path(line); }); return paths; } /** * File should contain the header to precompile and use while parsing * @returns the first path in the file */ Path userDefinedPchIncludeForFile(const QString& sourcefile) { static const QString pchIncludeFilename = QStringLiteral(".kdev_pch_include"); const auto paths = readPathListFile(findConfigFile(sourcefile, pchIncludeFilename)); return paths.isEmpty() ? Path() : paths.first(); } ProjectFileItem* findProjectFileItem(const IndexedString& url, bool* hasBuildSystemInfo) { ProjectFileItem* file = nullptr; *hasBuildSystemInfo = false; for (auto project: ICore::self()->projectController()->projects()) { auto files = project->filesForPath(url); if (files.isEmpty()) { continue; } file = files.last(); // A file might be defined in different targets. // Prefer file items defined inside a target with non-empty includes. for (auto f: files) { if (!dynamic_cast(f->parent())) { continue; } file = f; if (!IDefinesAndIncludesManager::manager()->includes(f, IDefinesAndIncludesManager::ProjectSpecific).isEmpty()) { break; } } } if (file && file->project()) { if (auto bsm = file->project()->buildSystemManager()) { *hasBuildSystemInfo = bsm->hasIncludesOrDefines(file); } } return file; } ClangParsingEnvironmentFile* parsingEnvironmentFile(const TopDUContext* context) { return dynamic_cast(context->parsingEnvironmentFile().data()); } DocumentChangeTracker* trackerForUrl(const IndexedString& url) { return ICore::self()->languageController()->backgroundParser()->trackerForUrl(url); } } ClangParseJob::ClangParseJob(const IndexedString& url, ILanguageSupport* languageSupport) : ParseJob(url, languageSupport) { const auto tuUrl = clang()->index()->translationUnitForUrl(url); bool hasBuildSystemInfo; if (auto file = findProjectFileItem(tuUrl, &hasBuildSystemInfo)) { m_environment.addIncludes(IDefinesAndIncludesManager::manager()->includes(file)); m_environment.addDefines(IDefinesAndIncludesManager::manager()->defines(file)); m_environment.setParserSettings(ClangSettingsManager::self()->parserSettings(file)); } else { m_environment.addIncludes(IDefinesAndIncludesManager::manager()->includes(tuUrl.str())); m_environment.addDefines(IDefinesAndIncludesManager::manager()->defines(tuUrl.str())); m_environment.setParserSettings(ClangSettingsManager::self()->parserSettings(nullptr)); } const bool isSource = ClangHelpers::isSource(tuUrl.str()); m_environment.setQuality( isSource ? (hasBuildSystemInfo ? ClangParsingEnvironment::BuildSystem : ClangParsingEnvironment::Source) : ClangParsingEnvironment::Unknown ); m_environment.setTranslationUnitUrl(tuUrl); Path::List projectPaths; const auto& projects = ICore::self()->projectController()->projects(); projectPaths.reserve(projects.size()); foreach (auto project, projects) { projectPaths.append(project->path()); } m_environment.setProjectPaths(projectPaths); foreach(auto document, ICore::self()->documentController()->openDocuments()) { auto textDocument = document->textDocument(); if (!textDocument || !textDocument->isModified() || !textDocument->url().isLocalFile() || !DocumentFinderHelpers::mimeTypesList().contains(textDocument->mimeType())) { continue; } m_unsavedFiles << UnsavedFile(textDocument->url().toLocalFile(), textDocument->textLines(textDocument->documentRange())); const IndexedString indexedUrl(textDocument->url()); m_unsavedRevisions.insert(indexedUrl, ModificationRevision::revisionForFile(indexedUrl)); } if (auto tracker = trackerForUrl(url)) { tracker->reset(); } } ClangSupport* ClangParseJob::clang() const { return static_cast(languageSupport()); } void ClangParseJob::run(ThreadWeaver::JobPointer /*self*/, ThreadWeaver::Thread */*thread*/) { QReadLocker parseLock(languageSupport()->parseLock()); if (abortRequested()) { return; } { const auto tuUrlStr = m_environment.translationUnitUrl().str(); m_environment.addIncludes(IDefinesAndIncludesManager::manager()->includesInBackground(tuUrlStr)); m_environment.addDefines(IDefinesAndIncludesManager::manager()->definesInBackground(tuUrlStr)); m_environment.setPchInclude(userDefinedPchIncludeForFile(tuUrlStr)); } if (abortRequested()) { return; } + // NOTE: we must have all declarations, contexts and uses available for files that are opened in the editor + // it is very hard to check this for all included files of this TU, and previously lead to problems + // when we tried to skip function bodies as an optimization for files that where not open in the editor. + // now, we always build everything, which is correct but a tad bit slower. we can try to optimize later. + setMinimumFeatures(static_cast(minimumFeatures() | TopDUContext::AllDeclarationsContextsAndUses)); + if (minimumFeatures() & AttachASTWithoutUpdating) { // The context doesn't need to be updated, but has no AST attached (restored from disk), // so attach AST to it, without updating DUChain ParseSession session(createSessionData()); DUChainWriteLocker lock; auto ctx = DUChainUtils::standardContextForUrl(document().toUrl()); if (!ctx) { clangDebug() << "Lost context while attaching AST"; return; } ctx->setAst(IAstContainer::Ptr(session.data())); if (minimumFeatures() & UpdateHighlighting) { lock.unlock(); languageSupport()->codeHighlighting()->highlightDUChain(ctx); } return; } { UrlParseLock urlLock(document()); if (abortRequested() || !isUpdateRequired(ParseSession::languageString())) { return; } } ParseSession session(ClangIntegration::DUChainUtils::findParseSessionData(document(), m_environment.translationUnitUrl())); if (abortRequested()) { return; } if (!session.data() || !session.reparse(m_unsavedFiles, m_environment)) { session.setData(createSessionData()); } if (!session.unit()) { // failed to parse file, unpin and don't try again clang()->index()->unpinTranslationUnitForUrl(document()); return; } if (!clang_getFile(session.unit(), document().byteArray().constData())) { // this parse job's document does not exist in the pinned translation unit // so we need to unpin and re-add this document // Ideally we'd reset m_environment and session, but this is much simpler // and shouldn't be a common case clang()->index()->unpinTranslationUnitForUrl(document()); if (!(minimumFeatures() & Rescheduled)) { auto features = static_cast(minimumFeatures() | Rescheduled); ICore::self()->languageController()->backgroundParser()->addDocument(document(), features, priority()); } return; } Imports imports = ClangHelpers::tuImports(session.unit()); IncludeFileContexts includedFiles; if (auto pch = clang()->index()->pch(m_environment)) { auto pchFile = pch->mapFile(session.unit()); includedFiles = pch->mapIncludes(session.unit()); includedFiles.insert(pchFile, pch->context()); auto tuFile = clang_getFile(session.unit(), m_environment.translationUnitUrl().byteArray().constData()); imports.insert(tuFile, { pchFile, CursorInRevision(0, 0) } ); } if (abortRequested()) { return; } auto context = ClangHelpers::buildDUChain(session.mainFile(), imports, session, minimumFeatures(), includedFiles, clang()->index()); setDuChain(context); if (abortRequested()) { return; } { if (minimumFeatures() & TopDUContext::AST) { DUChainWriteLocker lock; context->setAst(IAstContainer::Ptr(session.data())); } #ifdef QT_DEBUG DUChainReadLocker lock; auto file = parsingEnvironmentFile(context); Q_ASSERT(file); // verify that features and environment where properly set in ClangHelpers::buildDUChain Q_ASSERT(file->featuresSatisfied(TopDUContext::Features(minimumFeatures() & ~TopDUContext::ForceUpdateRecursive))); + if (trackerForUrl(context->url())) { + Q_ASSERT(file->featuresSatisfied(TopDUContext::AllDeclarationsContextsAndUses)); + } #endif } foreach(const auto& context, includedFiles) { if (!context) { continue; } { // prefer the editor modification revision, instead of the on-disk revision auto it = m_unsavedRevisions.find(context->url()); if (it != m_unsavedRevisions.end()) { DUChainWriteLocker lock; auto file = parsingEnvironmentFile(context); Q_ASSERT(file); file->setModificationRevision(it.value()); } } if (trackerForUrl(context->url())) { if (clang()->index()->translationUnitForUrl(context->url()) == m_environment.translationUnitUrl()) { // cache the parse session and the contained translation unit for this chain // this then allows us to quickly reparse the document if it is changed by // the user // otherwise no editor component is open for this document and we can dispose // the TU to save memory // share the session data with all contexts that are pinned to this TU DUChainWriteLocker lock; context->setAst(IAstContainer::Ptr(session.data())); } languageSupport()->codeHighlighting()->highlightDUChain(context); } } } ParseSessionData::Ptr ClangParseJob::createSessionData() const { - const bool skipFunctionBodies = (minimumFeatures() <= TopDUContext::VisibleDeclarationsAndContexts); - return ParseSessionData::Ptr(new ParseSessionData(m_unsavedFiles, clang()->index(), m_environment, - (skipFunctionBodies ? ParseSessionData::SkipFunctionBodies : ParseSessionData::NoOption))); + return ParseSessionData::Ptr(new ParseSessionData(m_unsavedFiles, clang()->index(), m_environment, ParseSessionData::NoOption)); } const ParsingEnvironment* ClangParseJob::environment() const { return &m_environment; } diff --git a/languages/clang/clangsettings/clangsettingsmanager.cpp b/languages/clang/clangsettings/clangsettingsmanager.cpp index feb0b99878..acb7a5c5ad 100644 --- a/languages/clang/clangsettings/clangsettingsmanager.cpp +++ b/languages/clang/clangsettings/clangsettingsmanager.cpp @@ -1,130 +1,128 @@ /* * This file is part of KDevelop * * Copyright 2015 Sergey Kalinichev * * 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 "clangsettingsmanager.h" #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; namespace { const QString settingsGroup = QStringLiteral("Clang Settings"); const QString macros = QStringLiteral("macros"); const QString lookAhead = QStringLiteral("lookAhead"); const QString forwardDeclare = QStringLiteral("forwardDeclare"); AssistantsSettings readAssistantsSettings(KConfig* cfg) { auto grp = cfg->group(settingsGroup); AssistantsSettings settings; settings.forwardDeclare = grp.readEntry(forwardDeclare, true); return settings; } CodeCompletionSettings readCodeCompletionSettings(KConfig* cfg) { auto grp = cfg->group(settingsGroup); CodeCompletionSettings settings; settings.macros = grp.readEntry(macros, true); settings.lookAhead = grp.readEntry(lookAhead, false); return settings; } } ClangSettingsManager* ClangSettingsManager::self() { static ClangSettingsManager manager; return &manager; } AssistantsSettings ClangSettingsManager::assistantsSettings() const { auto cfg = ICore::self()->activeSession()->config(); return readAssistantsSettings(cfg.data()); } CodeCompletionSettings ClangSettingsManager::codeCompletionSettings() const { if (m_enableTesting) { CodeCompletionSettings settings; settings.lookAhead = true; settings.macros = true; return settings; } auto cfg = ICore::self()->activeSession()->config(); return readCodeCompletionSettings(cfg.data()); } ParserSettings ClangSettingsManager::parserSettings(KDevelop::ProjectBaseItem* item) const { - Q_ASSERT(QThread::currentThread() == qApp->thread()); - return {IDefinesAndIncludesManager::manager()->parserArguments(item)}; } ClangSettingsManager::ClangSettingsManager() {} bool ParserSettings::isCpp() const { return parserOptions.contains(QStringLiteral("-std=c++")); } QVector ParserSettings::toClangAPI() const { // TODO: This is not efficient. auto list = parserOptions.split(QLatin1Char(' '), QString::SkipEmptyParts); QVector result; result.reserve(list.size()); std::transform(list.constBegin(), list.constEnd(), std::back_inserter(result), [] (const QString &argument) { return argument.toUtf8(); }); return result; } bool ParserSettings::operator==(const ParserSettings& rhs) const { return parserOptions == rhs.parserOptions; } diff --git a/languages/clang/codecompletion/model.cpp b/languages/clang/codecompletion/model.cpp index 5856392cda..724d9be693 100644 --- a/languages/clang/codecompletion/model.cpp +++ b/languages/clang/codecompletion/model.cpp @@ -1,203 +1,201 @@ /* * This file is part of KDevelop * Copyright 2014 Milian Wolff * * 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 "model.h" #include "util/clangdebug.h" #include "context.h" #include "includepathcompletioncontext.h" #include "duchain/parsesession.h" #include "duchain/clangindex.h" #include "duchain/duchainutils.h" #include #include #include #include #include #include #include using namespace KDevelop; namespace { bool isSpaceOnly(const QString& string) { return std::find_if(string.begin(), string.end(), [] (const QChar c) { return !c.isSpace(); }) == string.end(); } bool includePathCompletionRequired(const QString& text) { QString line; const int idx = text.lastIndexOf(QLatin1Char('\n')); if (idx != -1) { line = text.mid(idx + 1).trimmed(); } else { line = text.trimmed(); } const static QRegularExpression includeRegExp(QStringLiteral("^\\s*#\\s*include")); if (!line.contains(includeRegExp)) { return false; } return true; } QSharedPointer createCompletionContext(const DUContextPointer& context, const ParseSessionData::Ptr& session, const QUrl& url, const KTextEditor::Cursor& position, const QString& text, const QString& followingText) { if (includePathCompletionRequired(text)) { return QSharedPointer::create(context, session, url, position, text); } else { return QSharedPointer::create(context, session, url, position, text, followingText); } } class ClangCodeCompletionWorker : public CodeCompletionWorker { Q_OBJECT public: ClangCodeCompletionWorker(ClangIndex* index, CodeCompletionModel* model) : CodeCompletionWorker(model) , m_index(index) {} virtual ~ClangCodeCompletionWorker() = default; public slots: void completionRequested(const QUrl &url, const KTextEditor::Cursor& position, const QString& text, const QString& followingText) { aborting() = false; DUChainReadLocker lock; if (aborting()) { failed(); return; } auto top = DUChainUtils::standardContextForUrl(url); if (!top) { qCWarning(KDEV_CLANG) << "No context found for" << url; return; } ParseSessionData::Ptr sessionData(ClangIntegration::DUChainUtils::findParseSessionData(top->url(), m_index->translationUnitForUrl(top->url()))); if (!sessionData) { // TODO: trigger reparse and re-request code completion qCWarning(KDEV_CLANG) << "No parse session / AST attached to context for url" << url; return; } if (aborting()) { failed(); return; } // We hold DUChain lock, and ask for ParseSession, but TUDUChain indirectly holds ParseSession lock. lock.unlock(); auto completionContext = ::createCompletionContext(DUContextPointer(top), sessionData, url, position, text, followingText); lock.lock(); if (aborting()) { failed(); return; } bool abort = false; // NOTE: cursor might be wrong here, but shouldn't matter much I hope... // when the document changed significantly, then the cache is off anyways and we don't get anything sensible // the position here is just a "optimization" to only search up to that position const auto& items = completionContext->completionItems(abort); if (aborting()) { failed(); return; } auto tree = computeGroups( items, {} ); if (aborting()) { failed(); return; } tree += completionContext->ungroupedElements(); foundDeclarations( tree, {} ); } private: ClangIndex* m_index; }; } ClangCodeCompletionModel::ClangCodeCompletionModel(ClangIndex* index, QObject* parent) : CodeCompletionModel(parent) , m_index(index) { qRegisterMetaType(); } ClangCodeCompletionModel::~ClangCodeCompletionModel() { } bool ClangCodeCompletionModel::shouldStartCompletion(KTextEditor::View* view, const QString& inserted, bool userInsertion, const KTextEditor::Cursor& position) { - if ( inserted.isEmpty() || isSpaceOnly(inserted) || - inserted.endsWith(QLatin1Char(';')) || inserted.endsWith(QLatin1Char('}')) || - inserted.endsWith(QLatin1Char(']')) || inserted.endsWith(QLatin1Char(')')) || - inserted.endsWith(QLatin1Char(' ')) ) - { - return false; - } + static const QString noCompletionAfter = QStringLiteral(";{}]) "); + + if (inserted.isEmpty() || isSpaceOnly(inserted) || noCompletionAfter.contains(inserted.at(inserted.size() - 1))) { + return false; + } return KDevelop::CodeCompletionModel::shouldStartCompletion(view, inserted, userInsertion, position); } CodeCompletionWorker* ClangCodeCompletionModel::createCompletionWorker() { auto worker = new ClangCodeCompletionWorker(m_index, this); connect(this, &ClangCodeCompletionModel::requestCompletion, worker, &ClangCodeCompletionWorker::completionRequested); return worker; } void ClangCodeCompletionModel::completionInvokedInternal(KTextEditor::View* view, const KTextEditor::Range& range, CodeCompletionModel::InvocationType /*invocationType*/, const QUrl &url) { auto text = view->document()->text({0, 0, range.start().line(), range.start().column()}); auto followingText = view->document()->text({{range.start().line(), range.start().column()}, view->document()->documentEnd()}); emit requestCompletion(url, KTextEditor::Cursor(range.start()), text, followingText); } #include "model.moc" #include "moc_model.cpp" diff --git a/languages/clang/duchain/builder.cpp b/languages/clang/duchain/builder.cpp index 76879255a1..9bde7910fc 100644 --- a/languages/clang/duchain/builder.cpp +++ b/languages/clang/duchain/builder.cpp @@ -1,1402 +1,1402 @@ /* * This file is part of KDevelop * * Copyright 2013 Olivier de Gaalon * Copyright 2015 Milian Wolff * * This library 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 library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "builder.h" #include "util/clangdebug.h" #include "templatehelpers.h" #include "cursorkindtraits.h" #include "clangducontext.h" #include "macrodefinition.h" #include "types/classspecializationtype.h" #include "util/clangdebug.h" #include "util/clangutils.h" #include "util/clangtypes.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /// Turn on for debugging the declaration building #define IF_DEBUG(x) namespace { // TODO: this is ugly, can we find a better alternative? static bool s_jsonTestRun = false; //BEGIN helpers /** * Find the cursor that cursor @p cursor references * * First tries to get the referenced cursor via clang_getCursorReferenced, * and if that fails, tries to get them via clang_getOverloadedDecl * (which returns the referenced cursor for CXCursor_OverloadedDeclRef, for example) * * @return Valid cursor on success, else null cursor */ CXCursor referencedCursor(CXCursor cursor) { auto referenced = clang_getCursorReferenced(cursor); if (!clang_equalCursors(cursor, referenced)) { return referenced; } // get the first result for now referenced = clang_getOverloadedDecl(cursor, 0); if (!clang_Cursor_isNull(referenced)) { return referenced; } return clang_getNullCursor(); } Identifier makeId(CXCursor cursor) { return Identifier(ClangString(clang_getCursorSpelling(cursor)).toIndexed()); } QByteArray makeComment(CXComment comment) { if (Q_UNLIKELY(s_jsonTestRun)) { auto kind = clang_Comment_getKind(comment); if (kind == CXComment_Text) return ClangString(clang_TextComment_getText(comment)).toByteArray(); QByteArray text; int numChildren = clang_Comment_getNumChildren(comment); for (int i = 0; i < numChildren; ++i) text += makeComment(clang_Comment_getChild(comment, i)); return text; } return ClangString(clang_FullComment_getAsHTML(comment)).toByteArray(); } AbstractType* createDelayedType(CXType type) { auto t = new DelayedType; QString typeName = ClangString(clang_getTypeSpelling(type)).toString(); typeName.remove(QStringLiteral("const ")); typeName.remove(QStringLiteral("volatile ")); t->setIdentifier(IndexedTypeIdentifier(typeName)); return t; } void contextImportDecl(DUContext* context, const DeclarationPointer& decl) { auto top = context->topContext(); if (auto import = decl->logicalInternalContext(top)) { context->addImportedParentContext(import); context->topContext()->updateImportsCache(); } } //END helpers CXChildVisitResult visitCursor(CXCursor cursor, CXCursor parent, CXClientData data); //BEGIN IdType template struct IdType; template struct IdType::type> { typedef StructureType Type; }; template struct IdType::type> { typedef TypeAliasType Type; }; template struct IdType::type> { typedef TypeAliasType Type; }; template struct IdType::type> { typedef EnumerationType Type; }; template struct IdType::type> { typedef EnumeratorType Type; }; //END IdType //BEGIN DeclType template struct DeclType; template struct DeclType::type> { typedef Declaration Type; }; template struct DeclType::type> { typedef MacroDefinition Type; }; template struct DeclType::type> { typedef ForwardDeclaration Type; }; template struct DeclType::type> { typedef ClassDeclaration Type; }; template struct DeclType::type> { typedef ClassFunctionDeclaration Type; }; template struct DeclType::type> { typedef FunctionDeclaration Type; }; template struct DeclType::type> { typedef FunctionDefinition Type; }; template struct DeclType::type> { typedef NamespaceAliasDeclaration Type; }; template struct DeclType::type> { typedef ClassMemberDeclaration Type; }; //END DeclType //BEGIN CurrentContext struct CurrentContext { CurrentContext(DUContext* context) : context(context) { DUChainReadLocker lock; previousChildContexts = context->childContexts(); previousChildDeclarations = context->localDeclarations(); } ~CurrentContext() { DUChainWriteLocker lock; qDeleteAll(previousChildContexts); qDeleteAll(previousChildDeclarations); } DUContext* context; // when updatig, this contains child contexts of the current parent context QVector previousChildContexts; // when updatig, this contains child declarations of the current parent context QVector previousChildDeclarations; }; //END CurrentContext //BEGIN Visitor struct Visitor { explicit Visitor(CXTranslationUnit tu, CXFile file, const IncludeFileContexts& includes, const bool update); AbstractType *makeType(CXType type, CXCursor parent); AbstractType::Ptr makeAbsType(CXType type, CXCursor parent) { return AbstractType::Ptr(makeType(type, parent)); } //BEGIN dispatch* template = dummy> CXChildVisitResult dispatchCursor(CXCursor cursor, CXCursor parent); template = dummy> CXChildVisitResult dispatchCursor(CXCursor cursor, CXCursor parent); template = dummy> CXChildVisitResult dispatchCursor(CXCursor cursor, CXCursor parent); template AbstractType *dispatchType(CXType type, CXCursor cursor) { IF_DEBUG(clangDebug() << "TK:" << type.kind;) auto kdevType = createType(type, cursor); if (kdevType) { setTypeModifiers(type, kdevType); } return kdevType; } //BEGIN dispatch* //BEGIN build* template CXChildVisitResult buildDeclaration(CXCursor cursor); CXChildVisitResult buildUse(CXCursor cursor); CXChildVisitResult buildMacroExpansion(CXCursor cursor); CXChildVisitResult buildCompoundStatement(CXCursor cursor); CXChildVisitResult buildCXXBaseSpecifier(CXCursor cursor); CXChildVisitResult buildParmDecl(CXCursor cursor); //END build* //BEGIN create* template DeclType* createDeclarationCommon(CXCursor cursor, const Identifier& id) { auto range = ClangHelpers::cursorSpellingNameRange(cursor, id); if (id.isEmpty()) { // This is either an anonymous function parameter e.g.: void f(int); // Or anonymous struct/class/union e.g.: struct {} anonymous; // Set empty range for it range.end = range.start; } // check if cursor is inside a macro expansion auto clangRange = clang_Cursor_getSpellingNameRange(cursor, 0, 0); unsigned int expansionLocOffset; const auto spellingLocation = clang_getRangeStart(clangRange); clang_getExpansionLocation(spellingLocation, nullptr, nullptr, nullptr, &expansionLocOffset); if (m_macroExpansionLocations.contains(expansionLocOffset)) { unsigned int spellingLocOffset; clang_getSpellingLocation(spellingLocation, nullptr, nullptr, nullptr, &spellingLocOffset); // Set empty ranges for declarations inside macro expansion if (spellingLocOffset == expansionLocOffset) { range.end = range.start; } } if (m_update) { const IndexedIdentifier indexedId(id); DUChainWriteLocker lock; auto it = m_parentContext->previousChildDeclarations.begin(); while (it != m_parentContext->previousChildDeclarations.end()) { auto decl = dynamic_cast(*it); if (decl && decl->indexedIdentifier() == indexedId) { decl->setRange(range); setDeclData(cursor, decl); m_cursorToDeclarationCache[clang_hashCursor(cursor)] = decl; m_parentContext->previousChildDeclarations.erase(it); return decl; } ++it; } } auto decl = new DeclType(range, nullptr); decl->setIdentifier(id); m_cursorToDeclarationCache[clang_hashCursor(cursor)] = decl; setDeclData(cursor, decl); { DUChainWriteLocker lock; decl->setContext(m_parentContext->context); } return decl; } template Declaration* createDeclaration(CXCursor cursor, const Identifier& id, DUContext *context) { auto decl = createDeclarationCommon(cursor, id); auto type = createType(cursor); DUChainWriteLocker lock; if (context) decl->setInternalContext(context); setDeclType(decl, type); setDeclInCtxtData(cursor, decl); return decl; } template DUContext* createContext(CXCursor cursor, const QualifiedIdentifier& scopeId = {}) { // wtf: why is the DUContext API requesting a QID when it needs a plain Id?! // see: testNamespace auto range = ClangRange(clang_getCursorExtent(cursor)).toRangeInRevision(); DUChainWriteLocker lock; if (m_update) { const IndexedQualifiedIdentifier indexedScopeId(scopeId); auto it = m_parentContext->previousChildContexts.begin(); while (it != m_parentContext->previousChildContexts.end()) { auto ctx = *it; if (ctx->type() == Type && ctx->indexedLocalScopeIdentifier() == indexedScopeId) { ctx->setRange(range); m_parentContext->previousChildContexts.erase(it); return ctx; } ++it; } } //TODO: (..type, id..) constructor for DUContext? auto context = new ClangNormalDUContext(range, m_parentContext->context); context->setType(Type); context->setLocalScopeIdentifier(scopeId); if (Type == DUContext::Other || Type == DUContext::Function) context->setInSymbolTable(false); if (CK == CXCursor_CXXMethod || CursorKindTraits::isClass(CK)) { CXCursor semParent = clang_getCursorSemanticParent(cursor); if (!clang_Cursor_isNull(semParent)) { auto semParentDecl = findDeclaration(semParent); if (semParentDecl) { contextImportDecl(context, semParentDecl); } } } return context; } template = dummy> AbstractType *createType(CXType, CXCursor) { // TODO: would be nice to instantiate a ConstantIntegralType here and set a value if possible // but unfortunately libclang doesn't offer API to that // also see http://marc.info/?l=cfe-commits&m=131609142917881&w=2 return new IntegralType(CursorKindTraits::integralType(TK)); } template = dummy> AbstractType *createType(CXType type, CXCursor parent) { auto ptr = new PointerType; ptr->setBaseType(makeAbsType(clang_getPointeeType(type), parent)); return ptr; } template = dummy> AbstractType *createType(CXType type, CXCursor parent) { auto arr = new ArrayType; arr->setDimension((TK == CXType_IncompleteArray || TK == CXType_VariableArray || TK == CXType_DependentSizedArray) ? 0 : clang_getArraySize(type)); arr->setElementType(makeAbsType(clang_getArrayElementType(type), parent)); return arr; } template = dummy> AbstractType *createType(CXType type, CXCursor parent) { auto ref = new ReferenceType; ref->setIsRValue(type.kind == CXType_RValueReference); ref->setBaseType(makeAbsType(clang_getPointeeType(type), parent)); return ref; } template = dummy> AbstractType *createType(CXType type, CXCursor parent) { auto func = new FunctionType; func->setReturnType(makeAbsType(clang_getResultType(type), parent)); const int numArgs = clang_getNumArgTypes(type); for (int i = 0; i < numArgs; ++i) { func->addArgument(makeAbsType(clang_getArgType(type, i), parent)); } if (clang_isFunctionTypeVariadic(type)) { auto type = new DelayedType; static const auto id = IndexedTypeIdentifier(QStringLiteral("...")); type->setIdentifier(id); type->setKind(DelayedType::Unresolved); func->addArgument(AbstractType::Ptr(type)); } return func; } template = dummy> AbstractType *createType(CXType type, CXCursor parent) { DeclarationPointer decl = findDeclaration(clang_getTypeDeclaration(type)); DUChainReadLocker lock; if (!decl) { // probably a forward-declared type decl = ClangHelpers::findForwardDeclaration(type, m_parentContext->context, parent); } if (clang_Type_getNumTemplateArguments(type) != -1) { return createClassTemplateSpecializationType(type, decl); } auto t = new StructureType; t->setDeclaration(decl.data()); return t; } template = dummy> AbstractType *createType(CXType type, CXCursor) { auto t = new EnumerationType; setIdTypeDecl(clang_getTypeDeclaration(type), t); return t; } template = dummy> AbstractType *createType(CXType type, CXCursor parent) { auto t = new TypeAliasType; CXCursor location = clang_getTypeDeclaration(type); t->setType(makeAbsType(clang_getTypedefDeclUnderlyingType(location), parent)); setIdTypeDecl(location, t); return t; } template = dummy> AbstractType *createType(CXType, CXCursor /*parent*/) { auto t = new DelayedType; static const IndexedTypeIdentifier id(QLatin1String(CursorKindTraits::delayedTypeName(TK))); t->setIdentifier(id); return t; } template = dummy> AbstractType *createType(CXType type, CXCursor /*parent*/) { return createDelayedType(type); } template = dummy> AbstractType *createType(CXType type, CXCursor parent) { auto numTA = clang_Type_getNumTemplateArguments(type); if (numTA != -1) { // This is a class template specialization. return createClassTemplateSpecializationType(type); } // Maybe it's the ElaboratedType. E.g.: "struct Type foo();" or "NS::Type foo();" or "void foo(enum Enum e);" e.t.c. auto oldType = type; type = clang_getCanonicalType(type); bool isElaboratedType = type.kind != CXType_FunctionProto && type.kind != CXType_FunctionNoProto && type.kind != CXType_Unexposed && type.kind != CXType_Invalid && type.kind != CXType_Record; return !isElaboratedType ? createDelayedType(oldType) : makeType(type, parent); } template = dummy> typename IdType::Type *createType(CXCursor) { return new typename IdType::Type; } template = dummy> EnumeratorType *createType(CXCursor cursor) { auto type = new EnumeratorType; type->setValue(clang_getEnumConstantDeclUnsignedValue(cursor)); return type; } template = dummy> TypeAliasType *createType(CXCursor cursor) { auto type = new TypeAliasType; type->setType(makeAbsType(clang_getTypedefDeclUnderlyingType(cursor), cursor)); return type; } template = dummy> AbstractType* createType(CXCursor cursor) { auto clangType = clang_getCursorType(cursor); #if CINDEX_VERSION_MINOR < 31 if (clangType.kind == CXType_Unexposed) { // Clang sometimes can return CXType_Unexposed for CXType_FunctionProto kind. E.g. if it's AttributedType. return dispatchType(clangType, cursor); } #endif return makeType(clangType, cursor); } template = dummy> AbstractType *createType(CXCursor) { auto t = new DelayedType; static const IndexedTypeIdentifier id(QStringLiteral("Label")); t->setIdentifier(id); return t; } template = dummy> AbstractType *createType(CXCursor cursor) { auto clangType = clang_getCursorType(cursor); return makeType(clangType, cursor); } /// @param declaration an optional declaration that will be associated with created type AbstractType* createClassTemplateSpecializationType(CXType type, const DeclarationPointer declaration = {}) { auto numTA = clang_Type_getNumTemplateArguments(type); Q_ASSERT(numTA != -1); auto typeDecl = clang_getTypeDeclaration(type); if (!declaration && typeDecl.kind == CXCursor_NoDeclFound) { // clang_getTypeDeclaration doesn't handle all types, fall back to delayed type... return createDelayedType(type); } QStringList typesStr; QString tStr = ClangString(clang_getTypeSpelling(type)).toString(); ParamIterator iter(QStringLiteral("<>"), tStr); while (iter) { typesStr.append(*iter); ++iter; } auto cst = new ClassSpecializationType; for (int i = 0; i < numTA; i++) { auto argumentType = clang_Type_getTemplateArgumentAsType(type, i); AbstractType::Ptr currentType; if (argumentType.kind == CXType_Invalid) { if(i >= typesStr.size()){ currentType = createDelayedType(argumentType); } else { auto t = new DelayedType; t->setIdentifier(IndexedTypeIdentifier(typesStr[i])); currentType = t; } } else { currentType = makeType(argumentType, typeDecl); } if (currentType) { cst->addParameter(currentType->indexed()); } } auto decl = declaration ? declaration : findDeclaration(typeDecl); DUChainReadLocker lock; cst->setDeclaration(decl.data()); return cst; } //END create* //BEGIN setDeclData template void setDeclData(CXCursor cursor, Declaration *decl, bool setComment = true) const; template void setDeclData(CXCursor cursor, MacroDefinition* decl) const; template void setDeclData(CXCursor cursor, ClassMemberDeclaration *decl) const; template = dummy> void setDeclData(CXCursor cursor, ClassDeclaration* decl) const; template = dummy> void setDeclData(CXCursor cursor, ClassDeclaration* decl) const; template void setDeclData(CXCursor cursor, AbstractFunctionDeclaration* decl) const; template void setDeclData(CXCursor cursor, ClassFunctionDeclaration* decl) const; template void setDeclData(CXCursor cursor, FunctionDeclaration *decl, bool setComment = true) const; template void setDeclData(CXCursor cursor, FunctionDefinition *decl) const; template void setDeclData(CXCursor cursor, NamespaceAliasDeclaration *decl) const; //END setDeclData //BEGIN setDeclInCtxtData template void setDeclInCtxtData(CXCursor, Declaration*) { //No-op } template void setDeclInCtxtData(CXCursor cursor, ClassFunctionDeclaration *decl) { // HACK to retrieve function-constness // This looks like a bug in Clang -- In theory setTypeModifiers should take care of setting the const modifier // however, clang_isConstQualifiedType() for TK == CXType_FunctionProto always returns false // TODO: Debug further auto type = decl->abstractType(); if (type) { if (clang_CXXMethod_isConst(cursor)) { type->setModifiers(type->modifiers() | AbstractType::ConstModifier); decl->setAbstractType(type); } } } template void setDeclInCtxtData(CXCursor cursor, FunctionDefinition *def) { setDeclInCtxtData(cursor, static_cast(def)); const CXCursor canon = clang_getCanonicalCursor(cursor); if (auto decl = findDeclaration(canon)) { def->setDeclaration(decl.data()); } } //END setDeclInCtxtData //BEGIN setDeclType template void setDeclType(Declaration *decl, typename IdType::Type *type) { setDeclType(decl, static_cast(type)); setDeclType(decl, static_cast(type)); } template void setDeclType(Declaration *decl, IdentifiedType *type) { type->setDeclaration(decl); } template void setDeclType(Declaration *decl, AbstractType *type) { decl->setAbstractType(AbstractType::Ptr(type)); } //END setDeclType template void setTypeModifiers(CXType type, AbstractType* kdevType) const; const CXFile m_file; const IncludeFileContexts &m_includes; DeclarationPointer findDeclaration(CXCursor cursor) const; void setIdTypeDecl(CXCursor typeCursor, IdentifiedType* idType) const; std::unordered_map> m_uses; /// At these location offsets (cf. @ref clang_getExpansionLocation) we encountered macro expansions QSet m_macroExpansionLocations; mutable QHash m_cursorToDeclarationCache; CurrentContext *m_parentContext; const bool m_update; }; //BEGIN setTypeModifiers template void Visitor::setTypeModifiers(CXType type, AbstractType* kdevType) const { quint64 modifiers = 0; if (clang_isConstQualifiedType(type)) { modifiers |= AbstractType::ConstModifier; } if (clang_isVolatileQualifiedType(type)) { modifiers |= AbstractType::VolatileModifier; } if (TK == CXType_Short || TK == CXType_UShort) { modifiers |= AbstractType::ShortModifier; } if (TK == CXType_Long || TK == CXType_LongDouble || TK == CXType_ULong) { modifiers |= AbstractType::LongModifier; } if (TK == CXType_LongLong || TK == CXType_ULongLong) { modifiers |= AbstractType::LongLongModifier; } if (TK == CXType_SChar) { modifiers |= AbstractType::SignedModifier; } if (TK == CXType_UChar || TK == CXType_UInt || TK == CXType_UShort || TK == CXType_UInt128 || TK == CXType_ULong || TK == CXType_ULongLong) { modifiers |= AbstractType::UnsignedModifier; } kdevType->setModifiers(modifiers); } //END setTypeModifiers //BEGIN dispatchCursor template> CXChildVisitResult Visitor::dispatchCursor(CXCursor cursor, CXCursor parent) { const bool decision = CursorKindTraits::isClass(clang_getCursorKind(parent)); return decision ? dispatchCursor(cursor, parent) : dispatchCursor(cursor, parent); } template> CXChildVisitResult Visitor::dispatchCursor(CXCursor cursor, CXCursor parent) { IF_DEBUG(clangDebug() << "IsInClass:" << IsInClass << "- isDefinition:" << IsDefinition;) const bool isDefinition = clang_isCursorDefinition(cursor); return isDefinition ? dispatchCursor(cursor, parent) : dispatchCursor(cursor, parent); } template> CXChildVisitResult Visitor::dispatchCursor(CXCursor cursor, CXCursor parent) { IF_DEBUG(clangDebug() << "IsInClass:" << IsInClass << "- isDefinition:" << IsDefinition;) // We may end up visiting the same cursor twice in some cases // see discussion on https://git.reviewboard.kde.org/r/119526/ // TODO: Investigate why this is happening in libclang if ((CursorKindTraits::isClass(CK) || CK == CXCursor_EnumDecl) && clang_getCursorKind(parent) == CXCursor_VarDecl) { return CXChildVisit_Continue; } constexpr bool isClassMember = IsInClass == Decision::True; constexpr bool isDefinition = IsDefinition == Decision::True; constexpr bool hasContext = CursorKindTraits::isFunction(CK) || (IsDefinition == Decision::True); return buildDeclaration::Type, hasContext>(cursor); } //END dispatchCursor //BEGIN setDeclData template void Visitor::setDeclData(CXCursor cursor, Declaration *decl, bool setComment) const { if (setComment) decl->setComment(makeComment(clang_Cursor_getParsedComment(cursor))); if (CursorKindTraits::isAliasType(CK)) { decl->setIsTypeAlias(true); } if (CK == CXCursor_Namespace) decl->setKind(Declaration::Namespace); if (CK == CXCursor_EnumDecl || CK == CXCursor_EnumConstantDecl || CursorKindTraits::isClass(CK) || CursorKindTraits::isAliasType(CK)) decl->setKind(Declaration::Type); int isAlwaysDeprecated; clang_getCursorPlatformAvailability(cursor, &isAlwaysDeprecated, nullptr, nullptr, nullptr, nullptr, 0); decl->setDeprecated(isAlwaysDeprecated); } template void Visitor::setDeclData(CXCursor cursor, MacroDefinition* decl) const { setDeclData(cursor, static_cast(decl)); if (m_update) { decl->clearParameters(); } auto unit = clang_Cursor_getTranslationUnit(cursor); auto range = clang_getCursorExtent(cursor); // TODO: Quite lacking API in libclang here. // No way to find out if this macro is function-like or not // cf. http://clang.llvm.org/doxygen/classclang_1_1MacroInfo.html // And no way to get the actual definition text range // Should be quite easy to expose that in libclang, though // Let' still get some basic support for this and parse on our own, it's not that difficult const QString contents = QString::fromUtf8(ClangUtils::getRawContents(unit, range)); const int firstOpeningParen = contents.indexOf(QLatin1Char('(')); const int firstWhitespace = contents.indexOf(QLatin1Char(' ')); const bool isFunctionLike = (firstOpeningParen != -1) && (firstOpeningParen < firstWhitespace); decl->setFunctionLike(isFunctionLike); // now extract the actual definition text int start = -1; if (isFunctionLike) { const int closingParen = findClose(contents, firstOpeningParen); if (closingParen != -1) { start = closingParen + 2; // + ')' + ' ' // extract macro function parameters const QString parameters = contents.mid(firstOpeningParen, closingParen - firstOpeningParen + 1); ParamIterator paramIt(QStringLiteral("():"), parameters, 0); while (paramIt) { decl->addParameter(IndexedString(*paramIt)); ++paramIt; } } } else { start = firstWhitespace + 1; // + ' ' } if (start == -1) { // unlikely: invalid macro definition, insert the complete #define statement decl->setDefinition(IndexedString(QLatin1String("#define ") + contents)); } else if (start < contents.size()) { decl->setDefinition(IndexedString(contents.mid(start))); } // else: macro has no body => leave the definition text empty } template void Visitor::setDeclData(CXCursor cursor, ClassMemberDeclaration *decl) const { setDeclData(cursor, static_cast(decl)); //A CXCursor_VarDecl in a class is static (otherwise it'd be a CXCursor_FieldDecl) if (CK == CXCursor_VarDecl) decl->setStatic(true); decl->setAccessPolicy(CursorKindTraits::kdevAccessPolicy(clang_getCXXAccessSpecifier(cursor))); #if CINDEX_VERSION_MINOR >= 30 if (!s_jsonTestRun) { auto offset = clang_Cursor_getOffsetOfField(cursor); if (offset >= 0) { // don't add this info to the json tests, it invalidates the comment structure auto type = clang_getCursorType(cursor); auto sizeOf = clang_Type_getSizeOf(type); auto alignedTo = clang_Type_getAlignOf(type); decl->setComment(decl->comment() + i18n("
offset in parent: %1 Bit
" "size: %2 Bytes
" "aligned to: %3 Bytes", offset, sizeOf, alignedTo).toUtf8()); } } #endif } template> void Visitor::setDeclData(CXCursor cursor, ClassDeclaration* decl) const { CXCursorKind kind = clang_getTemplateCursorKind(cursor); switch (kind) { case CXCursor_UnionDecl: setDeclData(cursor, decl); break; case CXCursor_StructDecl: setDeclData(cursor, decl); break; case CXCursor_ClassDecl: setDeclData(cursor, decl); break; default: Q_ASSERT(false); break; } } template> void Visitor::setDeclData(CXCursor cursor, ClassDeclaration* decl) const { if (m_update) { decl->clearBaseClasses(); } setDeclData(cursor, static_cast(decl)); if (CK == CXCursor_UnionDecl) decl->setClassType(ClassDeclarationData::Union); if (CK == CXCursor_StructDecl) decl->setClassType(ClassDeclarationData::Struct); if (clang_isCursorDefinition(cursor)) { decl->setDeclarationIsDefinition(true); } if (!s_jsonTestRun) { // don't add this info to the json tests, it invalidates the comment structure auto type = clang_getCursorType(cursor); auto sizeOf = clang_Type_getSizeOf(type); auto alignOf = clang_Type_getAlignOf(type); if (sizeOf >= 0 && alignOf >= 0) { decl->setComment(decl->comment() + i18n("
size: %1 Bytes
" "aligned to: %2 Bytes", sizeOf, alignOf).toUtf8()); } } } template void Visitor::setDeclData(CXCursor cursor, AbstractFunctionDeclaration* decl) const { if (m_update) { decl->clearDefaultParameters(); } // No setDeclData(...) here: AbstractFunctionDeclaration is an interface // TODO: Can we get the default arguments directly from Clang? // also see http://clang-developers.42468.n3.nabble.com/Finding-default-value-for-function-argument-with-clang-c-API-td4036919.html const QVector defaultArgs = ClangUtils::getDefaultArguments(cursor, ClangUtils::MinimumSize); foreach (const QString& defaultArg, defaultArgs) { decl->addDefaultParameter(IndexedString(defaultArg)); } } template void Visitor::setDeclData(CXCursor cursor, ClassFunctionDeclaration* decl) const { setDeclData(cursor, static_cast(decl)); setDeclData(cursor, static_cast(decl)); decl->setAbstract(clang_CXXMethod_isPureVirtual(cursor)); decl->setStatic(clang_CXXMethod_isStatic(cursor)); decl->setVirtual(clang_CXXMethod_isVirtual(cursor)); } template void Visitor::setDeclData(CXCursor cursor, FunctionDeclaration *decl, bool setComment) const { setDeclData(cursor, static_cast(decl)); setDeclData(cursor, static_cast(decl), setComment); } template void Visitor::setDeclData(CXCursor cursor, FunctionDefinition *decl) const { bool setComment = clang_equalCursors(clang_getCanonicalCursor(cursor), cursor); setDeclData(cursor, static_cast(decl), setComment); } template void Visitor::setDeclData(CXCursor cursor, NamespaceAliasDeclaration *decl) const { setDeclData(cursor, static_cast(decl)); clang_visitChildren(cursor, [] (CXCursor cursor, CXCursor /*parent*/, CXClientData data) -> CXChildVisitResult { Q_ASSERT(clang_getCursorKind(cursor) == CXCursor_NamespaceRef); const auto id = QualifiedIdentifier(ClangString(clang_getCursorSpelling(cursor)).toString()); reinterpret_cast(data)->setImportIdentifier(id); return CXChildVisit_Break; }, decl); } //END setDeclData //BEGIN build* template CXChildVisitResult Visitor::buildDeclaration(CXCursor cursor) { auto id = makeId(cursor); if (CK == CXCursor_UnexposedDecl && id.isEmpty()) { // skip unexposed declarations that have no identifier set // this is useful to skip e.g. friend declarations - return CXChildVisit_Continue; + return CXChildVisit_Recurse; } IF_DEBUG(clangDebug() << "id:" << id << "- CK:" << CK << "- DeclType:" << typeid(DeclType).name() << "- hasContext:" << hasContext;) // Code path for class declarations that may be defined "out-of-line", e.g. // "SomeNameSpace::SomeClass {};" QScopedPointer helperContext; if (CursorKindTraits::isClass(CK) || CursorKindTraits::isFunction(CK)) { const auto lexicalParent = clang_getCursorLexicalParent(cursor); const auto semanticParent = clang_getCursorSemanticParent(cursor); const bool isOutOfLine = !clang_equalCursors(lexicalParent, semanticParent); if (isOutOfLine) { const QString scope = ClangUtils::getScope(cursor); auto context = createContext(cursor, QualifiedIdentifier(scope)); helperContext.reset(new CurrentContext(context)); } } // if helperContext is null, this is a no-op PushValue pushCurrent(m_parentContext, helperContext.isNull() ? m_parentContext : helperContext.data()); if (hasContext) { auto context = createContext(cursor, QualifiedIdentifier(id)); createDeclaration(cursor, id, context); CurrentContext newParent(context); PushValue pushCurrent(m_parentContext, &newParent); clang_visitChildren(cursor, &visitCursor, this); return CXChildVisit_Continue; } createDeclaration(cursor, id, nullptr); return CXChildVisit_Recurse; } CXChildVisitResult Visitor::buildParmDecl(CXCursor cursor) { return buildDeclaration::Type, false>(cursor); } CXChildVisitResult Visitor::buildUse(CXCursor cursor) { m_uses[m_parentContext->context].push_back(cursor); return cursor.kind == CXCursor_DeclRefExpr || cursor.kind == CXCursor_MemberRefExpr ? CXChildVisit_Recurse : CXChildVisit_Continue; } CXChildVisitResult Visitor::buildMacroExpansion(CXCursor cursor) { buildUse(cursor); // cache that we encountered a macro expansion at this location unsigned int offset; clang_getSpellingLocation(clang_getCursorLocation(cursor), nullptr, nullptr, nullptr, &offset); m_macroExpansionLocations << offset; return CXChildVisit_Recurse; } CXChildVisitResult Visitor::buildCompoundStatement(CXCursor cursor) { if (m_parentContext->context->type() == DUContext::Function) { auto context = createContext(cursor); CurrentContext newParent(context); PushValue pushCurrent(m_parentContext, &newParent); clang_visitChildren(cursor, &visitCursor, this); return CXChildVisit_Continue; } return CXChildVisit_Recurse; } CXChildVisitResult Visitor::buildCXXBaseSpecifier(CXCursor cursor) { auto currentContext = m_parentContext->context; bool virtualInherited = clang_isVirtualBase(cursor); Declaration::AccessPolicy access = CursorKindTraits::kdevAccessPolicy(clang_getCXXAccessSpecifier(cursor)); auto classDeclCursor = clang_getCursorReferenced(cursor); auto decl = findDeclaration(classDeclCursor); if (!decl) { // this happens for templates with template-dependent base classes e.g. - dunno whether we can/should do more here clangDebug() << "failed to find declaration for base specifier:" << clang_getCursorDisplayName(cursor); return CXChildVisit_Recurse; } DUChainWriteLocker lock; contextImportDecl(currentContext, decl); auto classDecl = dynamic_cast(currentContext->owner()); Q_ASSERT(classDecl); classDecl->addBaseClass({decl->indexedType(), access, virtualInherited}); return CXChildVisit_Recurse; } //END build* DeclarationPointer Visitor::findDeclaration(CXCursor cursor) const { const auto cursorHash = clang_hashCursor(cursor); const auto it = m_cursorToDeclarationCache.constFind(cursorHash); if (it != m_cursorToDeclarationCache.constEnd()) { return *it; } // fallback, and cache result auto decl = ClangHelpers::findDeclaration(cursor, m_includes); m_cursorToDeclarationCache.insert(cursorHash, decl); return decl; } void Visitor::setIdTypeDecl(CXCursor typeCursor, IdentifiedType* idType) const { DeclarationPointer decl = findDeclaration(typeCursor); DUChainReadLocker lock; if (decl) { idType->setDeclaration(decl.data()); } } AbstractType *Visitor::makeType(CXType type, CXCursor parent) { #define UseKind(TypeKind) case TypeKind: return dispatchType(type, parent) switch (type.kind) { UseKind(CXType_Void); UseKind(CXType_Bool); UseKind(CXType_Short); UseKind(CXType_UShort); UseKind(CXType_Int); UseKind(CXType_UInt); UseKind(CXType_Long); UseKind(CXType_ULong); UseKind(CXType_LongLong); UseKind(CXType_ULongLong); UseKind(CXType_Float); UseKind(CXType_LongDouble); UseKind(CXType_Double); UseKind(CXType_Char_U); UseKind(CXType_Char_S); UseKind(CXType_UChar); UseKind(CXType_SChar); UseKind(CXType_Char16); UseKind(CXType_Char32); UseKind(CXType_Pointer); UseKind(CXType_MemberPointer); UseKind(CXType_ObjCObjectPointer); UseKind(CXType_ConstantArray); UseKind(CXType_VariableArray); UseKind(CXType_IncompleteArray); UseKind(CXType_DependentSizedArray); UseKind(CXType_LValueReference); UseKind(CXType_RValueReference); UseKind(CXType_FunctionProto); UseKind(CXType_Record); UseKind(CXType_Enum); UseKind(CXType_Typedef); UseKind(CXType_Int128); UseKind(CXType_UInt128); UseKind(CXType_Vector); UseKind(CXType_Unexposed); UseKind(CXType_WChar); UseKind(CXType_ObjCInterface); UseKind(CXType_ObjCId); UseKind(CXType_ObjCClass); UseKind(CXType_ObjCSel); UseKind(CXType_NullPtr); case CXType_Invalid: return nullptr; default: qCWarning(KDEV_CLANG) << "Unhandled type:" << type.kind << clang_getTypeSpelling(type); return nullptr; } } RangeInRevision rangeInRevisionForUse(CXCursor cursor, CXCursorKind referencedCursorKind, CXSourceRange useRange, const QSet& macroExpansionLocations) { auto range = ClangRange(useRange).toRangeInRevision(); //TODO: Fix in clang, happens for operator<<, operator<, probably more if (clang_Range_isNull(useRange)) { useRange = clang_getCursorExtent(cursor); range = ClangRange(useRange).toRangeInRevision(); } if (referencedCursorKind == CXCursor_ConversionFunction) { range.end = range.start; range.start.column--; } // For uses inside macro expansions, create an empty use range at the spelling location // the empty range is required in order to not "overlap" the macro expansion range // and to allow proper navigation for the macro expansion // also see JSON test 'macros.cpp' if (clang_getCursorKind(cursor) != CXCursor_MacroExpansion) { unsigned int expansionLocOffset; const auto spellingLocation = clang_getRangeStart(useRange); clang_getExpansionLocation(spellingLocation, nullptr, nullptr, nullptr, &expansionLocOffset); if (macroExpansionLocations.contains(expansionLocOffset)) { unsigned int spellingLocOffset; clang_getSpellingLocation(spellingLocation, nullptr, nullptr, nullptr, &spellingLocOffset); if (spellingLocOffset == expansionLocOffset) { range.end = range.start; } } } else { // Workaround for wrong use range returned by clang for macro expansions const auto contents = ClangUtils::getRawContents(clang_Cursor_getTranslationUnit(cursor), useRange); const int firstOpeningParen = contents.indexOf('('); if (firstOpeningParen != -1) { range.end.column = range.start.column + firstOpeningParen; range.end.line = range.start.line; } } return range; } Visitor::Visitor(CXTranslationUnit tu, CXFile file, const IncludeFileContexts& includes, const bool update) : m_file(file) , m_includes(includes) , m_parentContext(nullptr) , m_update(update) { CXCursor tuCursor = clang_getTranslationUnitCursor(tu); CurrentContext parent(includes[file]); m_parentContext = &parent; clang_visitChildren(tuCursor, &visitCursor, this); TopDUContext *top = m_parentContext->context->topContext(); if (m_update) { DUChainWriteLocker lock; top->deleteUsesRecursively(); } for (const auto &contextUses : m_uses) { for (const auto &cursor : contextUses.second) { auto referenced = clang_getCanonicalCursor(referencedCursor(cursor)); if (clang_Cursor_isNull(referenced)) { continue; } auto used = findDeclaration(referenced); if (!used) { DUChainReadLocker lock; DeclarationPointer decl = ClangHelpers::findForwardDeclaration(clang_getCursorType(referenced), contextUses.first, referenced); used = decl; if (!used) { continue; } } #if CINDEX_VERSION_MINOR >= 29 if (clang_Cursor_getNumTemplateArguments(referenced) >= 0) { // Ideally, we don't need this, but for function templates clang_getCanonicalCursor returns a function definition // See also the testUsesCreatedForDeclarations test DUChainReadLocker lock; used = DUChainUtils::declarationForDefinition(used.data()); } #endif const auto useRange = clang_getCursorReferenceNameRange(cursor, 0, 0); const auto range = rangeInRevisionForUse(cursor, referenced.kind, useRange, m_macroExpansionLocations); DUChainWriteLocker lock; auto usedIndex = top->indexForUsedDeclaration(used.data()); contextUses.first->createUse(usedIndex, range); } } } //END Visitor CXChildVisitResult visitCursor(CXCursor cursor, CXCursor parent, CXClientData data) { Visitor *visitor = static_cast(data); const auto kind = clang_getCursorKind(cursor); auto location = clang_getCursorLocation(cursor); CXFile file; clang_getFileLocation(location, &file, nullptr, nullptr, nullptr); // don't skip MemberRefExpr with invalid location, see also: // http://lists.cs.uiuc.edu/pipermail/cfe-dev/2015-May/043114.html if (!ClangUtils::isFileEqual(file, visitor->m_file) && (file || kind != CXCursor_MemberRefExpr)) { return CXChildVisit_Continue; } #define UseCursorKind(CursorKind, ...) case CursorKind: return visitor->dispatchCursor(__VA_ARGS__); switch (kind) { UseCursorKind(CXCursor_UnexposedDecl, cursor, parent); UseCursorKind(CXCursor_StructDecl, cursor, parent); UseCursorKind(CXCursor_UnionDecl, cursor, parent); UseCursorKind(CXCursor_ClassDecl, cursor, parent); UseCursorKind(CXCursor_EnumDecl, cursor, parent); UseCursorKind(CXCursor_FieldDecl, cursor, parent); UseCursorKind(CXCursor_EnumConstantDecl, cursor, parent); UseCursorKind(CXCursor_FunctionDecl, cursor, parent); UseCursorKind(CXCursor_VarDecl, cursor, parent); UseCursorKind(CXCursor_TypeAliasDecl, cursor, parent); UseCursorKind(CXCursor_TypedefDecl, cursor, parent); UseCursorKind(CXCursor_CXXMethod, cursor, parent); UseCursorKind(CXCursor_Namespace, cursor, parent); UseCursorKind(CXCursor_NamespaceAlias, cursor, parent); UseCursorKind(CXCursor_Constructor, cursor, parent); UseCursorKind(CXCursor_Destructor, cursor, parent); UseCursorKind(CXCursor_ConversionFunction, cursor, parent); UseCursorKind(CXCursor_TemplateTypeParameter, cursor, parent); UseCursorKind(CXCursor_NonTypeTemplateParameter, cursor, parent); UseCursorKind(CXCursor_TemplateTemplateParameter, cursor, parent); UseCursorKind(CXCursor_FunctionTemplate, cursor, parent); UseCursorKind(CXCursor_ClassTemplate, cursor, parent); UseCursorKind(CXCursor_ClassTemplatePartialSpecialization, cursor, parent); UseCursorKind(CXCursor_ObjCInterfaceDecl, cursor, parent); UseCursorKind(CXCursor_ObjCCategoryDecl, cursor, parent); UseCursorKind(CXCursor_ObjCProtocolDecl, cursor, parent); UseCursorKind(CXCursor_ObjCPropertyDecl, cursor, parent); UseCursorKind(CXCursor_ObjCIvarDecl, cursor, parent); UseCursorKind(CXCursor_ObjCInstanceMethodDecl, cursor, parent); UseCursorKind(CXCursor_ObjCClassMethodDecl, cursor, parent); UseCursorKind(CXCursor_ObjCImplementationDecl, cursor, parent); UseCursorKind(CXCursor_ObjCCategoryImplDecl, cursor, parent); UseCursorKind(CXCursor_MacroDefinition, cursor, parent); UseCursorKind(CXCursor_LabelStmt, cursor, parent); case CXCursor_TypeRef: case CXCursor_TemplateRef: case CXCursor_NamespaceRef: case CXCursor_MemberRef: case CXCursor_LabelRef: case CXCursor_OverloadedDeclRef: case CXCursor_VariableRef: case CXCursor_DeclRefExpr: case CXCursor_MemberRefExpr: case CXCursor_ObjCClassRef: return visitor->buildUse(cursor); case CXCursor_MacroExpansion: return visitor->buildMacroExpansion(cursor); case CXCursor_CompoundStmt: return visitor->buildCompoundStatement(cursor); case CXCursor_CXXBaseSpecifier: return visitor->buildCXXBaseSpecifier(cursor); case CXCursor_ParmDecl: return visitor->buildParmDecl(cursor); default: return CXChildVisit_Recurse; } } } namespace Builder { void visit(CXTranslationUnit tu, CXFile file, const IncludeFileContexts& includes, const bool update) { Visitor visitor(tu, file, includes, update); } void enableJSONTestRun() { s_jsonTestRun = true; } } \ No newline at end of file diff --git a/languages/clang/duchain/parsesession.cpp b/languages/clang/duchain/parsesession.cpp index 49c6b062d9..a8ab3fd8a1 100644 --- a/languages/clang/duchain/parsesession.cpp +++ b/languages/clang/duchain/parsesession.cpp @@ -1,350 +1,348 @@ /* This file is part of KDevelop Copyright 2013 Olivier de Gaalon Copyright 2013 Milian Wolff Copyright 2013 Kevin Funk This library 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 library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "parsesession.h" #include "clangproblem.h" #include "clangdiagnosticevaluator.h" #include "todoextractor.h" #include "clanghelpers.h" #include "clangindex.h" #include "clangparsingenvironment.h" #include "util/clangdebug.h" #include "util/clangtypes.h" #include "util/clangutils.h" #include #include #include #include #include #include #include using namespace KDevelop; namespace { QVector argsForSession(const QString& path, ParseSessionData::Options options, const ParserSettings& parserSettings) { QMimeDatabase db; if(db.mimeTypeForFile(path).name() == QStringLiteral("text/x-objcsrc")) { return {QByteArrayLiteral("-xobjective-c++")}; } - // this can happen for unit tests that use the ParseSession directly if (parserSettings.parserOptions.isEmpty()) { - return { - QByteArrayLiteral("-fspell-checking"), - QByteArrayLiteral("-Wdocumentation"), - QByteArrayLiteral("-std=c++11"), - QByteArrayLiteral("-xc++"), - QByteArrayLiteral("-Wall"), - QByteArrayLiteral("-Wunused-parameter"), - QByteArrayLiteral("-Wunreachable-code"), - QByteArrayLiteral("-nostdinc"), - QByteArrayLiteral("-nostdinc++"), - QByteArrayLiteral("-ferror-limit=100") - }; + // The parserOptions can be empty for some unit tests that use ParseSession directly + auto defaultArguments = ClangSettingsManager::self()->parserSettings(nullptr).toClangAPI(); + Q_ASSERT(!defaultArguments.isEmpty()); + defaultArguments.append(QByteArrayLiteral("-nostdinc")); + defaultArguments.append(QByteArrayLiteral("-nostdinc++")); + defaultArguments.append(QByteArrayLiteral("-xc++")); + return defaultArguments; } auto result = parserSettings.toClangAPI(); result.append(QByteArrayLiteral("-nostdinc")); result.append(QByteArrayLiteral("-nostdinc++")); if (options & ParseSessionData::PrecompiledHeader) { result.append(parserSettings.isCpp() ? QByteArrayLiteral("-xc++-header") : QByteArrayLiteral("-xc-header")); return result; } result.append(parserSettings.isCpp() ? QByteArrayLiteral("-xc++") : QByteArrayLiteral("-xc")); return result; } void addIncludes(QVector* args, QVector* otherArgs, const Path::List& includes, const char* cliSwitch) { foreach (const Path& url, includes) { QFileInfo info(url.toLocalFile()); QByteArray path = url.toLocalFile().toUtf8(); if (info.isFile()) { path.prepend("-include"); } else { path.prepend(cliSwitch); } otherArgs->append(path); args->append(path.constData()); } } QVector toClangApi(const QVector& unsavedFiles) { QVector unsaved; unsaved.reserve(unsavedFiles.size()); std::transform(unsavedFiles.begin(), unsavedFiles.end(), std::back_inserter(unsaved), [] (const UnsavedFile& file) { return file.toClangApi(); }); return unsaved; } } ParseSessionData::ParseSessionData(const QVector& unsavedFiles, ClangIndex* index, const ClangParsingEnvironment& environment, Options options) : m_file(nullptr) , m_unit(nullptr) { unsigned int flags = CXTranslationUnit_CXXChainedPCH | CXTranslationUnit_DetailedPreprocessingRecord; if (options.testFlag(SkipFunctionBodies)) { flags |= CXTranslationUnit_SkipFunctionBodies; } if (options.testFlag(PrecompiledHeader)) { flags |= CXTranslationUnit_ForSerialization; } else { flags |= CXTranslationUnit_CacheCompletionResults | CXTranslationUnit_PrecompiledPreamble | CXTranslationUnit_Incomplete; } const auto tuUrl = environment.translationUnitUrl(); Q_ASSERT(!tuUrl.isEmpty()); const auto arguments = argsForSession(tuUrl.str(), options, environment.parserSettings()); QVector clangArguments; const auto& includes = environment.includes(); const auto& pchInclude = environment.pchInclude(); // uses QByteArray as smart-pointer for const char* ownership QVector smartArgs; smartArgs.reserve(includes.system.size() + includes.project.size() + pchInclude.isValid() + arguments.size() + 1); clangArguments.reserve(smartArgs.size()); std::transform(arguments.constBegin(), arguments.constEnd(), std::back_inserter(clangArguments), [] (const QByteArray &argument) { return argument.constData(); }); // NOTE: the PCH include must come before all other includes! if (pchInclude.isValid()) { clangArguments << "-include"; QByteArray pchFile = pchInclude.toLocalFile().toUtf8(); smartArgs << pchFile; clangArguments << pchFile.constData(); } addIncludes(&clangArguments, &smartArgs, includes.system, "-isystem"); addIncludes(&clangArguments, &smartArgs, includes.project, "-I"); m_definesFile.open(); QTextStream definesStream(&m_definesFile); Q_ASSERT(m_definesFile.isWritable()); const auto& defines = environment.defines(); for (auto it = defines.begin(); it != defines.end(); ++it) { definesStream << QStringLiteral("#define ") << it.key() << ' ' << it.value() << '\n'; } definesStream.flush(); smartArgs << m_definesFile.fileName().toUtf8(); clangArguments << "-imacros" << smartArgs.last().constData(); QVector unsaved; //For PrecompiledHeader, we don't want unsaved contents (and contents.isEmpty()) if (!options.testFlag(PrecompiledHeader)) { unsaved = toClangApi(unsavedFiles); } const CXErrorCode code = clang_parseTranslationUnit2( index->index(), tuUrl.byteArray().constData(), clangArguments.constData(), clangArguments.size(), unsaved.data(), unsaved.size(), flags, &m_unit ); if (code != CXError_Success) { qWarning() << "clang_parseTranslationUnit2 return with error code" << code; } if (m_unit) { setUnit(m_unit); m_environment = environment; if (options.testFlag(PrecompiledHeader)) { clang_saveTranslationUnit(m_unit, (tuUrl.byteArray() + ".pch").constData(), CXSaveTranslationUnit_None); } } else { qWarning() << "Failed to parse translation unit:" << tuUrl; } } ParseSessionData::~ParseSessionData() { clang_disposeTranslationUnit(m_unit); } void ParseSessionData::setUnit(CXTranslationUnit unit) { Q_ASSERT(!m_unit || unit == m_unit); m_unit = unit; const ClangString unitFile(clang_getTranslationUnitSpelling(unit)); m_file = clang_getFile(m_unit, unitFile.c_str()); } ClangParsingEnvironment ParseSessionData::environment() const { return m_environment; } ParseSession::ParseSession(const ParseSessionData::Ptr& data) : d(data) { if (d) { ENSURE_CHAIN_NOT_LOCKED d->m_mutex.lock(); } } ParseSession::~ParseSession() { if (d) { d->m_mutex.unlock(); } } void ParseSession::setData(const ParseSessionData::Ptr& data) { if (data == d) { return; } if (d) { d->m_mutex.unlock(); } d = data; if (d) { ENSURE_CHAIN_NOT_LOCKED d->m_mutex.lock(); } } ParseSessionData::Ptr ParseSession::data() const { return d; } IndexedString ParseSession::languageString() { static const IndexedString lang("Clang"); return lang; } QList ParseSession::problemsForFile(CXFile file) const { if (!d) { return {}; } QList problems; // extra clang diagnostics const uint numDiagnostics = clang_getNumDiagnostics(d->m_unit); problems.reserve(numDiagnostics); for (uint i = 0; i < numDiagnostics; ++i) { auto diagnostic = clang_getDiagnostic(d->m_unit, i); CXSourceLocation location = clang_getDiagnosticLocation(diagnostic); CXFile diagnosticFile; clang_getFileLocation(location, &diagnosticFile, nullptr, nullptr, nullptr); if (diagnosticFile != file) { continue; } ProblemPointer problem(ClangDiagnosticEvaluator::createProblem(diagnostic, d->m_unit)); problems << problem; clang_disposeDiagnostic(diagnostic); } - const QString path = QDir(ClangString(clang_getFileName(file)).toString()).canonicalPath(); - const IndexedString indexedPath(path); + // other problem sources TodoExtractor extractor(unit(), file); problems << extractor.problems(); - // other problem sources +#if CINDEX_VERSION_MINOR > 30 + // note that the below warning is triggered on every reparse when there is a precompiled preamble + // see also TestDUChain::testReparseIncludeGuard + const QString path = QDir(ClangString(clang_getFileName(file)).toString()).canonicalPath(); + const IndexedString indexedPath(path); if (ClangHelpers::isHeader(path) && !clang_isFileMultipleIncludeGuarded(unit(), file) && !clang_Location_isInSystemHeader(clang_getLocationForOffset(d->m_unit, file, 0))) { ProblemPointer problem(new Problem); problem->setSeverity(IProblem::Warning); problem->setDescription(i18n("Header is not guarded against multiple inclusions")); problem->setExplanation(i18n("The given header is not guarded against multiple inclusions, " "either with the conventional #ifndef/#define/#endif macro guards or with #pragma once.")); problem->setFinalLocation({indexedPath, KTextEditor::Range()}); problem->setSource(IProblem::Preprocessor); problems << problem; // TODO: Easy to add an assistant here that adds the guards -- any takers? } +#endif return problems; } CXTranslationUnit ParseSession::unit() const { return d ? d->m_unit : nullptr; } CXFile ParseSession::file(const QByteArray& path) const { return clang_getFile(unit(), path.constData()); } CXFile ParseSession::mainFile() const { return d ? d->m_file : nullptr; } bool ParseSession::reparse(const QVector& unsavedFiles, const ClangParsingEnvironment& environment) { if (!d || environment != d->m_environment) { return false; } auto unsaved = toClangApi(unsavedFiles); if (clang_reparseTranslationUnit(d->m_unit, unsaved.size(), unsaved.data(), clang_defaultReparseOptions(d->m_unit)) == 0) { d->setUnit(d->m_unit); return true; } else { return false; } } ClangParsingEnvironment ParseSession::environment() const { return d->m_environment; } diff --git a/languages/clang/tests/test_duchain.cpp b/languages/clang/tests/test_duchain.cpp index 78c37d3fdb..6bd3f38921 100644 --- a/languages/clang/tests/test_duchain.cpp +++ b/languages/clang/tests/test_duchain.cpp @@ -1,1292 +1,1368 @@ /* * Copyright 2014 Milian Wolff * Copyright 2014 Kevin Funk * Copyright 2015 Sergey Kalinichev * * 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 "test_duchain.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "duchain/clangparsingenvironmentfile.h" #include "duchain/clangparsingenvironment.h" #include "duchain/parsesession.h" #include #include QTEST_MAIN(TestDUChain); using namespace KDevelop; class TestEnvironmentProvider final : public IDefinesAndIncludesManager::BackgroundProvider { public: virtual ~TestEnvironmentProvider() = default; virtual QHash< QString, QString > definesInBackground(const QString& /*path*/) const override { return defines; } virtual Path::List includesInBackground(const QString& /*path*/) const override { return includes; } virtual IDefinesAndIncludesManager::Type type() const override { return IDefinesAndIncludesManager::UserDefined; } QHash defines; Path::List includes; }; TestDUChain::~TestDUChain() = default; void TestDUChain::initTestCase() { QLoggingCategory::setFilterRules(QStringLiteral("*.debug=false\ndefault.debug=true\nkdevelop.plugins.clang.debug=true\n")); QVERIFY(qputenv("KDEV_DISABLE_PLUGINS", "kdevcppsupport")); AutoTestShell::init({QStringLiteral("kdevclangsupport")}); TestCore::initialize(); } void TestDUChain::cleanupTestCase() { TestCore::shutdown(); } void TestDUChain::cleanup() { if (m_provider) { IDefinesAndIncludesManager::manager()->unregisterBackgroundProvider(m_provider.data()); } } void TestDUChain::init() { m_provider.reset(new TestEnvironmentProvider); IDefinesAndIncludesManager::manager()->registerBackgroundProvider(m_provider.data()); } struct ExpectedComment { QString identifier; QString comment; }; Q_DECLARE_METATYPE(ExpectedComment) Q_DECLARE_METATYPE(AbstractType::WhichType) void TestDUChain::testComments() { QFETCH(QString, code); QFETCH(ExpectedComment, expectedComment); TestFile file(code, "cpp"); QVERIFY(file.parseAndWait()); DUChainReadLocker lock; auto top = file.topContext(); QVERIFY(top); auto candidates = top->findDeclarations(QualifiedIdentifier(expectedComment.identifier)); QVERIFY(!candidates.isEmpty()); auto decl = candidates.first(); QString comment = QString::fromLocal8Bit(decl->comment()); comment = KDevelop::htmlToPlainText(comment, KDevelop::CompleteMode); QCOMPARE(comment, expectedComment.comment); } void TestDUChain::testComments_data() { QTest::addColumn("code"); QTest::addColumn("expectedComment"); // note: Clang only retrieves the comments when in doxygen-style format (i.e. '///', '/**', '///<') QTest::newRow("invalid1") << "//this is foo\nint foo;" << ExpectedComment{"foo", QString()}; QTest::newRow("invalid2") << "/*this is foo*/\nint foo;" << ExpectedComment{"foo", QString()}; QTest::newRow("basic1") << "///this is foo\nint foo;" << ExpectedComment{"foo", "this is foo"}; QTest::newRow("basic2") << "/**this is foo*/\nint foo;" << ExpectedComment{"foo", "this is foo"}; QTest::newRow("enumerator") << "enum Foo { bar1, ///localDeclarations().size(), 2); auto decl = file.topContext()->localDeclarations()[1]; QVERIFY(decl); auto function = dynamic_cast(decl); QVERIFY(function); auto functionType = function->type(); QVERIFY(functionType); QEXPECT_FAIL("namespace", "The ElaboratedType is not exposed through the libclang interface, not much we can do here", Abort); QVERIFY(functionType->returnType()->whichType() != AbstractType::TypeDelayed); QEXPECT_FAIL("typedef", "After using clang_getCanonicalType on ElaboratedType all typedef information get's stripped away", Continue); QCOMPARE(functionType->returnType()->whichType(), type); } void TestDUChain::testElaboratedType_data() { QTest::addColumn("code"); QTest::addColumn("type"); QTest::newRow("namespace") << "namespace NS{struct Type{};} struct NS::Type foo();" << AbstractType::TypeStructure; QTest::newRow("enum") << "enum Enum{}; enum Enum foo();" << AbstractType::TypeEnumeration; QTest::newRow("typedef") << "namespace NS{typedef int type;} NS::type foo();" << AbstractType::TypeAlias; } void TestDUChain::testInclude() { TestFile header("int foo() { return 42; }\n", "h"); // NOTE: header is _not_ explictly being parsed, instead the impl job does that TestFile impl("#include \"" + header.url().byteArray() + "\"\n" "int main() { return foo(); }", "cpp", &header); impl.parse(TopDUContext::AllDeclarationsContextsAndUses); auto implCtx = impl.topContext(); QVERIFY(implCtx); DUChainReadLocker lock; QCOMPARE(implCtx->localDeclarations().size(), 1); auto headerCtx = DUChain::self()->chainForDocument(header.url()); QVERIFY(headerCtx); QVERIFY(!headerCtx->parsingEnvironmentFile()->needsUpdate()); QCOMPARE(headerCtx->localDeclarations().size(), 1); QVERIFY(implCtx->imports(headerCtx, CursorInRevision(0, 10))); Declaration* foo = headerCtx->localDeclarations().first(); QCOMPARE(foo->uses().size(), 1); QCOMPARE(foo->uses().begin().key(), impl.url()); QCOMPARE(foo->uses().begin()->size(), 1); QCOMPARE(foo->uses().begin()->first(), RangeInRevision(1, 20, 1, 23)); } QByteArray createCode(const QByteArray& prefix, const int functions) { QByteArray code; code += "#ifndef " + prefix + "_H\n"; code += "#define " + prefix + "_H\n"; for (int i = 0; i < functions; ++i) { code += "void myFunc_" + prefix + "(int arg1, char arg2, const char* arg3);\n"; } code += "#endif\n"; return code; } void TestDUChain::testIncludeLocking() { TestFile header1(createCode("Header1", 1000), "h"); TestFile header2(createCode("Header2", 1000), "h"); TestFile header3(createCode("Header3", 1000), "h"); ICore::self()->languageController()->backgroundParser()->setThreadCount(3); TestFile impl1("#include \"" + header1.url().byteArray() + "\"\n" "#include \"" + header2.url().byteArray() + "\"\n" "#include \"" + header3.url().byteArray() + "\"\n" "int main() { return 0; }", "cpp"); TestFile impl2("#include \"" + header2.url().byteArray() + "\"\n" "#include \"" + header1.url().byteArray() + "\"\n" "#include \"" + header3.url().byteArray() + "\"\n" "int main() { return 0; }", "cpp"); TestFile impl3("#include \"" + header3.url().byteArray() + "\"\n" "#include \"" + header1.url().byteArray() + "\"\n" "#include \"" + header2.url().byteArray() + "\"\n" "int main() { return 0; }", "cpp"); impl1.parse(TopDUContext::AllDeclarationsContextsAndUses); impl2.parse(TopDUContext::AllDeclarationsContextsAndUses); impl3.parse(TopDUContext::AllDeclarationsContextsAndUses); QVERIFY(impl1.waitForParsed(5000)); QVERIFY(impl2.waitForParsed(5000)); QVERIFY(impl3.waitForParsed(5000)); DUChainReadLocker lock; QVERIFY(DUChain::self()->chainForDocument(header1.url())); QVERIFY(DUChain::self()->chainForDocument(header2.url())); QVERIFY(DUChain::self()->chainForDocument(header3.url())); } void TestDUChain::testReparse() { TestFile file("int main() { int i = 42; return i; }", "cpp"); file.parse(TopDUContext::AllDeclarationsContextsAndUses); DeclarationPointer mainDecl; DeclarationPointer iDecl; for (int i = 0; i < 3; ++i) { QVERIFY(file.waitForParsed(500)); DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->childContexts().size(), 1); QCOMPARE(file.topContext()->localDeclarations().size(), 1); DUContext *exprContext = file.topContext()->childContexts().first()->childContexts().first(); QCOMPARE(exprContext->localDeclarations().size(), 1); if (i) { QVERIFY(mainDecl); QCOMPARE(mainDecl.data(), file.topContext()->localDeclarations().first()); QVERIFY(iDecl); QCOMPARE(iDecl.data(), exprContext->localDeclarations().first()); } mainDecl = file.topContext()->localDeclarations().first(); iDecl = exprContext->localDeclarations().first(); QVERIFY(mainDecl->uses().isEmpty()); QCOMPARE(iDecl->uses().size(), 1); QCOMPARE(iDecl->uses().begin()->size(), 1); if (i == 1) { file.setFileContents("int main()\n{\nfloat i = 13; return i - 5;\n}\n"); } file.parse(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::ForceUpdateRecursive)); } } void TestDUChain::testReparseError() { TestFile file("int i = 1 / 0;\n", "cpp"); file.parse(TopDUContext::AllDeclarationsContextsAndUses); for (int i = 0; i < 2; ++i) { QVERIFY(file.waitForParsed(500)); DUChainReadLocker lock; QVERIFY(file.topContext()); if (!i) { QCOMPARE(file.topContext()->problems().size(), 1); file.setFileContents("int i = 0;\n"); } else { QCOMPARE(file.topContext()->problems().size(), 0); } file.parse(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::ForceUpdateRecursive)); } } void TestDUChain::testTemplate() { TestFile file("template struct foo { T bar; };\n" "int main() { foo myFoo; return myFoo.bar; }\n", "cpp"); QVERIFY(file.parseAndWait()); DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->localDeclarations().size(), 2); auto fooDecl = file.topContext()->localDeclarations().first(); QVERIFY(fooDecl->internalContext()); QCOMPARE(fooDecl->internalContext()->localDeclarations().size(), 2); QCOMPARE(file.topContext()->findDeclarations(QualifiedIdentifier("foo")).size(), 1); QCOMPARE(file.topContext()->findDeclarations(QualifiedIdentifier("foo::bar")).size(), 1); auto mainCtx = file.topContext()->localDeclarations().last()->internalContext()->childContexts().first(); QVERIFY(mainCtx); auto myFoo = mainCtx->localDeclarations().first(); QVERIFY(myFoo); QCOMPARE(myFoo->abstractType()->toString().remove(' '), QStringLiteral("foo")); } void TestDUChain::testNamespace() { TestFile file("namespace foo { struct bar { int baz; }; }\n" "int main() { foo::bar myBar; }\n", "cpp"); QVERIFY(file.parseAndWait()); DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->localDeclarations().size(), 2); auto fooDecl = file.topContext()->localDeclarations().first(); QVERIFY(fooDecl->internalContext()); QCOMPARE(fooDecl->internalContext()->localDeclarations().size(), 1); DUContext* top = file.topContext().data(); DUContext* mainCtx = file.topContext()->childContexts().last(); auto foo = top->localDeclarations().first(); QCOMPARE(foo->qualifiedIdentifier().toString(), QString("foo")); DUContext* fooCtx = file.topContext()->childContexts().first(); QCOMPARE(fooCtx->localScopeIdentifier().toString(), QString("foo")); QCOMPARE(fooCtx->scopeIdentifier(true).toString(), QString("foo")); QCOMPARE(fooCtx->localDeclarations().size(), 1); auto bar = fooCtx->localDeclarations().first(); QCOMPARE(bar->qualifiedIdentifier().toString(), QString("foo::bar")); QCOMPARE(fooCtx->childContexts().size(), 1); DUContext* barCtx = fooCtx->childContexts().first(); QCOMPARE(barCtx->localScopeIdentifier().toString(), QString("bar")); QCOMPARE(barCtx->scopeIdentifier(true).toString(), QString("foo::bar")); QCOMPARE(barCtx->localDeclarations().size(), 1); auto baz = barCtx->localDeclarations().first(); QCOMPARE(baz->qualifiedIdentifier().toString(), QString("foo::bar::baz")); for (auto ctx : {top, mainCtx}) { QCOMPARE(ctx->findDeclarations(QualifiedIdentifier("foo")).size(), 1); QCOMPARE(ctx->findDeclarations(QualifiedIdentifier("foo::bar")).size(), 1); QCOMPARE(ctx->findDeclarations(QualifiedIdentifier("foo::bar::baz")).size(), 1); } } void TestDUChain::testAutoTypeDeduction() { TestFile file("const volatile auto foo = 5;\n", "cpp"); QVERIFY(file.parseAndWait()); DUChainReadLocker lock; DUContext* ctx = file.topContext().data(); QVERIFY(ctx); QCOMPARE(ctx->localDeclarations().size(), 1); QCOMPARE(ctx->findDeclarations(QualifiedIdentifier("foo")).size(), 1); Declaration* decl = ctx->findDeclarations(QualifiedIdentifier("foo"))[0]; QCOMPARE(decl->identifier(), Identifier("foo")); #if CINDEX_VERSION_MINOR < 31 QEXPECT_FAIL("", "No type deduction here unfortunately, missing API in Clang", Continue); #endif QVERIFY(decl->type()); #if CINDEX_VERSION_MINOR < 31 QCOMPARE(decl->toString(), QStringLiteral("const volatile auto foo")); #else QCOMPARE(decl->toString(), QStringLiteral("const volatile int foo")); #endif } void TestDUChain::testTypeDeductionInTemplateInstantiation() { // see: http://clang-developers.42468.n3.nabble.com/RFC-missing-libclang-query-functions-features-td2504253.html TestFile file("template struct foo { T member; } foo f; auto i = f.member;", "cpp"); QVERIFY(file.parseAndWait()); DUChainReadLocker lock; DUContext* ctx = file.topContext().data(); QVERIFY(ctx); QCOMPARE(ctx->localDeclarations().size(), 3); Declaration* decl = 0; // check 'foo' declaration decl = ctx->localDeclarations()[0]; QVERIFY(decl); QCOMPARE(decl->identifier(), Identifier("foo")); // check type of 'member' inside declaration-scope QCOMPARE(ctx->childContexts().size(), 1); DUContext* fooCtx = ctx->childContexts().first(); QVERIFY(fooCtx); // Should there really be two declarations? QCOMPARE(fooCtx->localDeclarations().size(), 2); decl = fooCtx->localDeclarations()[1]; QCOMPARE(decl->identifier(), Identifier("member")); // check type of 'member' in definition of 'f' decl = ctx->localDeclarations()[1]; QCOMPARE(decl->identifier(), Identifier("f")); decl = ctx->localDeclarations()[2]; QCOMPARE(decl->identifier(), Identifier("i")); #if CINDEX_VERSION_MINOR < 31 QEXPECT_FAIL("", "No type deduction here unfortunately, missing API in Clang", Continue); #endif QVERIFY(decl->type()); } void TestDUChain::testVirtualMemberFunction() { //Forward-declarations with "struct" or "class" are considered equal, so make sure the override is detected correctly. TestFile file("struct S {}; struct A { virtual S* ret(); }; struct B : public A { virtual S* ret(); };", "cpp"); QVERIFY(file.parseAndWait()); DUChainReadLocker lock; DUContext* top = file.topContext().data(); QVERIFY(top); QCOMPARE(top->childContexts().count(), 3); QCOMPARE(top->localDeclarations().count(), 3); QCOMPARE(top->childContexts()[2]->localDeclarations().count(), 1); Declaration* decl = top->childContexts()[2]->localDeclarations()[0]; QCOMPARE(decl->identifier(), Identifier("ret")); QVERIFY(DUChainUtils::getOverridden(decl)); } void TestDUChain::testBaseClasses() { TestFile file("class Base {}; class Inherited : public Base {};", "cpp"); QVERIFY(file.parseAndWait()); DUChainReadLocker lock; DUContext* top = file.topContext().data(); QVERIFY(top); QCOMPARE(top->localDeclarations().count(), 2); Declaration* baseDecl = top->localDeclarations().first(); QCOMPARE(baseDecl->identifier(), Identifier("Base")); ClassDeclaration* inheritedDecl = dynamic_cast(top->localDeclarations()[1]); QCOMPARE(inheritedDecl->identifier(), Identifier("Inherited")); QVERIFY(inheritedDecl); QCOMPARE(inheritedDecl->baseClassesSize(), 1u); QCOMPARE(baseDecl->uses().count(), 1); QCOMPARE(baseDecl->uses().first().count(), 1); QCOMPARE(baseDecl->uses().first().first(), RangeInRevision(0, 40, 0, 44)); } void TestDUChain::testReparseBaseClasses() { TestFile file("struct a{}; struct b : a {};\n", "cpp"); file.parse(TopDUContext::AllDeclarationsContextsAndUses); for (int i = 0; i < 2; ++i) { qDebug() << "run: " << i; QVERIFY(file.waitForParsed(500)); DUChainWriteLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->childContexts().size(), 2); QCOMPARE(file.topContext()->childContexts().first()->importers().size(), 1); QCOMPARE(file.topContext()->childContexts().last()->importedParentContexts().size(), 1); QCOMPARE(file.topContext()->localDeclarations().size(), 2); auto aDecl = dynamic_cast(file.topContext()->localDeclarations().first()); QVERIFY(aDecl); QCOMPARE(aDecl->baseClassesSize(), 0u); auto bDecl = dynamic_cast(file.topContext()->localDeclarations().last()); QVERIFY(bDecl); QCOMPARE(bDecl->baseClassesSize(), 1u); int distance = 0; QVERIFY(bDecl->isPublicBaseClass(aDecl, file.topContext(), &distance)); QCOMPARE(distance, 1); file.parse(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::ForceUpdateRecursive)); } } void TestDUChain::testReparseBaseClassesTemplates() { TestFile file("template struct a{}; struct b : a {};\n", "cpp"); file.parse(TopDUContext::AllDeclarationsContextsAndUses); for (int i = 0; i < 2; ++i) { qDebug() << "run: " << i; QVERIFY(file.waitForParsed(500)); DUChainWriteLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->childContexts().size(), 2); QCOMPARE(file.topContext()->childContexts().first()->importers().size(), 1); QCOMPARE(file.topContext()->childContexts().last()->importedParentContexts().size(), 1); QCOMPARE(file.topContext()->localDeclarations().size(), 2); auto aDecl = dynamic_cast(file.topContext()->localDeclarations().first()); QVERIFY(aDecl); QCOMPARE(aDecl->baseClassesSize(), 0u); auto bDecl = dynamic_cast(file.topContext()->localDeclarations().last()); QVERIFY(bDecl); QCOMPARE(bDecl->baseClassesSize(), 1u); int distance = 0; QVERIFY(bDecl->isPublicBaseClass(aDecl, file.topContext(), &distance)); QCOMPARE(distance, 1); file.parse(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::ForceUpdateRecursive)); } } void TestDUChain::testGlobalFunctionDeclaration() { TestFile file("void foo(int arg1, char arg2);\n", "cpp"); file.parse(TopDUContext::AllDeclarationsContextsAndUses); file.waitForParsed(); DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->localDeclarations().size(), 1); QCOMPARE(file.topContext()->childContexts().size(), 1); QVERIFY(!file.topContext()->childContexts().first()->inSymbolTable()); } void TestDUChain::testFunctionDefinitionVsDeclaration() { TestFile file("void func(); void func() {}\n", "cpp"); file.parse(TopDUContext::AllDeclarationsContextsAndUses); QVERIFY(file.waitForParsed()); DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->localDeclarations().size(), 2); auto funcDecl = file.topContext()->localDeclarations()[0]; QVERIFY(!funcDecl->isDefinition()); QVERIFY(!dynamic_cast(funcDecl)); auto funcDef = file.topContext()->localDeclarations()[1]; QVERIFY(dynamic_cast(funcDef)); QVERIFY(funcDef->isDefinition()); } void TestDUChain::testEnsureNoDoubleVisit() { // On some language construct, we may up visiting the same cursor multiple times // Example: "struct SomeStruct {} s;" // decl: "SomeStruct SomeStruct " of kind StructDecl (2) in main.cpp@[(1,1),(1,17)] // decl: "struct SomeStruct s " of kind VarDecl (9) in main.cpp@[(1,1),(1,19)] // decl: "SomeStruct SomeStruct " of kind StructDecl (2) in main.cpp@[(1,1),(1,17)] // // => We end up visiting the StructDecl twice (or more) // That's because we use clang_visitChildren not just on the translation unit cursor. // Apparently just "recursing" vs. "visiting children explicitly" // results in a different AST traversal TestFile file("struct SomeStruct {} s;\n", "cpp"); file.parse(TopDUContext::AllDeclarationsContextsAndUses); QVERIFY(file.waitForParsed()); DUChainReadLocker lock; auto top = file.topContext(); QVERIFY(top); // there should only be one declaration for "SomeStruct" auto candidates = top->findDeclarations(QualifiedIdentifier("SomeStruct")); QCOMPARE(candidates.size(), 1); } void TestDUChain::testParsingEnvironment() { const TopDUContext::Features features = TopDUContext::AllDeclarationsContextsAndUses; IndexedTopDUContext indexed; ClangParsingEnvironment lastEnv; { TestFile file("int main() {}\n", "cpp"); auto astFeatures = static_cast(features | TopDUContext::AST); file.parse(astFeatures); file.setKeepDUChainData(true); QVERIFY(file.waitForParsed()); DUChainWriteLocker lock; auto top = file.topContext(); QVERIFY(top); auto sessionData = ParseSessionData::Ptr(dynamic_cast(top->ast().data())); lock.unlock(); ParseSession session(sessionData); lock.lock(); QVERIFY(session.data()); QVERIFY(top); auto envFile = QExplicitlySharedDataPointer( dynamic_cast(file.topContext()->parsingEnvironmentFile().data())); QCOMPARE(envFile->features(), astFeatures); QVERIFY(envFile->featuresSatisfied(astFeatures)); QCOMPARE(envFile->environmentQuality(), ClangParsingEnvironment::Source); // if no environment is given, no update should be triggered QVERIFY(!envFile->needsUpdate()); // same env should also not trigger a reparse ClangParsingEnvironment env = session.environment(); QCOMPARE(env.quality(), ClangParsingEnvironment::Source); QVERIFY(!envFile->needsUpdate(&env)); // but changing the environment should trigger an update env.addIncludes(Path::List() << Path("/foo/bar/baz")); QVERIFY(envFile->needsUpdate(&env)); envFile->setEnvironment(env); QVERIFY(!envFile->needsUpdate(&env)); // setting the environment quality higher should require an update env.setQuality(ClangParsingEnvironment::BuildSystem); QVERIFY(envFile->needsUpdate(&env)); envFile->setEnvironment(env); QVERIFY(!envFile->needsUpdate(&env)); // changing defines requires an update env.addDefines(QHash{ { "foo", "bar" } }); QVERIFY(envFile->needsUpdate(&env)); // but only when changing the defines for the envFile's TU const auto barTU = IndexedString("bar.cpp"); const auto oldTU = env.translationUnitUrl(); env.setTranslationUnitUrl(barTU); QCOMPARE(env.translationUnitUrl(), barTU); QVERIFY(!envFile->needsUpdate(&env)); env.setTranslationUnitUrl(oldTU); QVERIFY(envFile->needsUpdate(&env)); // update it again envFile->setEnvironment(env); QVERIFY(!envFile->needsUpdate(&env)); lastEnv = env; // now compare against a lower quality environment // in such a case, we do not want to trigger an update env.setQuality(ClangParsingEnvironment::Unknown); env.setTranslationUnitUrl(barTU); QVERIFY(!envFile->needsUpdate(&env)); // even when the environment changes env.addIncludes(Path::List() << Path("/lalalala")); QVERIFY(!envFile->needsUpdate(&env)); indexed = top->indexed(); } DUChain::self()->storeToDisk(); { DUChainWriteLocker lock; QVERIFY(!DUChain::self()->isInMemory(indexed.index())); QVERIFY(indexed.data()); QVERIFY(DUChain::self()->environmentFileForDocument(indexed)); auto envFile = QExplicitlySharedDataPointer( dynamic_cast(DUChain::self()->environmentFileForDocument(indexed).data())); QVERIFY(envFile); QCOMPARE(envFile->features(), features); QVERIFY(envFile->featuresSatisfied(features)); QVERIFY(!envFile->needsUpdate(&lastEnv)); DUChain::self()->removeDocumentChain(indexed.data()); } } void TestDUChain::testActiveDocumentHasASTAttached() { const TopDUContext::Features features = TopDUContext::AllDeclarationsContextsAndUses; IndexedTopDUContext indexed; ClangParsingEnvironment lastEnv; { TestFile file("int main() {}\n", "cpp"); auto astFeatures = static_cast(features | TopDUContext::AST); file.parse(astFeatures); file.setKeepDUChainData(true); QVERIFY(file.waitForParsed()); DUChainWriteLocker lock; auto top = file.topContext(); QVERIFY(top); auto sessionData = ParseSessionData::Ptr(dynamic_cast(top->ast().data())); lock.unlock(); ParseSession session(sessionData); lock.lock(); QVERIFY(session.data()); QVERIFY(top); QVERIFY(top->ast()); indexed = top->indexed(); } DUChain::self()->storeToDisk(); { DUChainWriteLocker lock; QVERIFY(!DUChain::self()->isInMemory(indexed.index())); QVERIFY(indexed.data()); } QUrl url; { DUChainReadLocker lock; auto ctx = indexed.data(); QVERIFY(ctx); QVERIFY(!ctx->ast()); url = ctx->url().toUrl(); } // Here the file is already deleted, so clang_parseTranslationUnit2 will fail, but we still get ParseSessionData attached. auto document = ICore::self()->documentController()->openDocument(url); QVERIFY(document); ICore::self()->documentController()->activateDocument(document); QApplication::processEvents(); ICore::self()->languageController()->backgroundParser()->parseDocuments(); QThread::sleep(1); document->close(KDevelop::IDocument::Discard); { DUChainReadLocker lock; auto ctx = indexed.data(); QVERIFY(ctx); QVERIFY(ctx->ast()); } DUChainWriteLocker lock; DUChain::self()->removeDocumentChain(indexed.data()); } void TestDUChain::testSystemIncludes() { ClangParsingEnvironment env; Path::List projectIncludes = { Path("/projects/1"), Path("/projects/1/sub"), Path("/projects/2"), Path("/projects/2/sub") }; env.addIncludes(projectIncludes); auto includes = env.includes(); // no project paths set, so everything is considered a system include QCOMPARE(includes.system, projectIncludes); QVERIFY(includes.project.isEmpty()); Path::List systemIncludes = { Path("/sys"), Path("/sys/sub") }; env.addIncludes(systemIncludes); includes = env.includes(); QCOMPARE(includes.system, projectIncludes + systemIncludes); QVERIFY(includes.project.isEmpty()); Path::List projects = { Path("/projects/1"), Path("/projects/2") }; env.setProjectPaths(projects); // now the list should be properly separated QCOMPARE(env.projectPaths(), projects); includes = env.includes(); QCOMPARE(includes.system, systemIncludes); QCOMPARE(includes.project, projectIncludes); } void TestDUChain::benchDUChainBuilder() { QBENCHMARK_ONCE { TestFile file( "#include \n" "#include \n" "#include \n" "#include \n" "#include \n" "#include \n" "#include \n" "#include \n" "#include \n" "#include \n", "cpp"); file.parse(TopDUContext::AllDeclarationsContextsAndUses); QVERIFY(file.waitForParsed(60000)); DUChainReadLocker lock; auto top = file.topContext(); QVERIFY(top); } } void TestDUChain::testReparseWithAllDeclarationsContextsAndUses() { TestFile file("int foo() { return 0; } int main() { return foo(); }", "cpp"); file.parse(TopDUContext::VisibleDeclarationsAndContexts); QVERIFY(file.waitForParsed(1000)); { DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->childContexts().size(), 2); QCOMPARE(file.topContext()->localDeclarations().size(), 2); - for (int i = 0; i < 2; ++i) { - auto dec = file.topContext()->localDeclarations().at(i); - QVERIFY(dec->uses().isEmpty()); - } + auto dec = file.topContext()->localDeclarations().at(0); + QEXPECT_FAIL("", "Skipping of function bodies is disabled for now", Continue); + QVERIFY(dec->uses().isEmpty()); } file.parse(TopDUContext::AllDeclarationsContextsAndUses); QVERIFY(file.waitForParsed(500)); { DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->childContexts().size(), 2); QCOMPARE(file.topContext()->localDeclarations().size(), 2); auto mainDecl = file.topContext()->localDeclarations()[1]; QVERIFY(mainDecl->uses().isEmpty()); auto foo = file.topContext()->localDeclarations().first(); QCOMPARE(foo->uses().size(), 1); } } void TestDUChain::testReparseOnDocumentActivated() { TestFile file("int foo() { return 0; } int main() { return foo(); }", "cpp"); file.parse(TopDUContext::VisibleDeclarationsAndContexts); QVERIFY(file.waitForParsed(1000)); { DUChainReadLocker lock; - QVERIFY(file.topContext()); - QCOMPARE(file.topContext()->childContexts().size(), 2); - QCOMPARE(file.topContext()->localDeclarations().size(), 2); + auto ctx = file.topContext(); + QVERIFY(ctx); + QCOMPARE(ctx->childContexts().size(), 2); + QCOMPARE(ctx->localDeclarations().size(), 2); - for (int i = 0; i < 2; ++i) { - auto dec = file.topContext()->localDeclarations().at(i); - QVERIFY(dec->uses().isEmpty()); - } + auto dec = ctx->localDeclarations().at(0); + QEXPECT_FAIL("", "Skipping of function bodies was disabled for now", Continue); + QVERIFY(dec->uses().isEmpty()); - QVERIFY(!file.topContext()->ast()); + QVERIFY(!ctx->ast()); } auto backgroundParser = ICore::self()->languageController()->backgroundParser(); QVERIFY(!backgroundParser->isQueued(file.url())); auto doc = ICore::self()->documentController()->openDocument(file.url().toUrl()); QVERIFY(doc); - QVERIFY(backgroundParser->isQueued(file.url())); - auto ctx = DUChain::self()->waitForUpdate(file.url(), TopDUContext::AllDeclarationsContextsAndUses); - QVERIFY(ctx); + QSignalSpy spy(backgroundParser, &BackgroundParser::parseJobFinished); + spy.wait(); + doc->close(KDevelop::IDocument::Discard); + { DUChainReadLocker lock; - qDebug() << (quint64)ctx->features(); + auto ctx = file.topContext(); QCOMPARE(ctx->features() & TopDUContext::AllDeclarationsContextsAndUses, static_cast(TopDUContext::AllDeclarationsContextsAndUses)); QVERIFY(ctx->topContext()->ast()); } } void TestDUChain::testReparseInclude() { TestFile header("int foo() { return 42; }\n", "h"); TestFile impl("#include \"" + header.url().byteArray() + "\"\n" "int main() { return foo(); }", "cpp", &header); // Use TopDUContext::AST to imitate that document is opened in the editor, so that ClangParseJob can store translation unit, that'll be used for reparsing. impl.parse(TopDUContext::Features(TopDUContext::AllDeclarationsAndContexts|TopDUContext::AST)); QVERIFY(impl.waitForParsed(5000)); { DUChainReadLocker lock; auto implCtx = impl.topContext(); QVERIFY(implCtx); QCOMPARE(implCtx->importedParentContexts().size(), 1); } impl.parse(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses|TopDUContext::AST)); QVERIFY(impl.waitForParsed(5000)); DUChainReadLocker lock; auto implCtx = impl.topContext(); QVERIFY(implCtx); QCOMPARE(implCtx->localDeclarations().size(), 1); QCOMPARE(implCtx->importedParentContexts().size(), 1); auto headerCtx = DUChain::self()->chainForDocument(header.url()); QVERIFY(headerCtx); QVERIFY(!headerCtx->parsingEnvironmentFile()->needsUpdate()); QCOMPARE(headerCtx->localDeclarations().size(), 1); QVERIFY(implCtx->imports(headerCtx, CursorInRevision(0, 10))); Declaration* foo = headerCtx->localDeclarations().first(); QCOMPARE(foo->uses().size(), 1); QCOMPARE(foo->uses().begin().key(), impl.url()); QCOMPARE(foo->uses().begin()->size(), 1); QCOMPARE(foo->uses().begin()->first(), RangeInRevision(1, 20, 1, 23)); QCOMPARE(DUChain::self()->allEnvironmentFiles(header.url()).size(), 1); QCOMPARE(DUChain::self()->allEnvironmentFiles(impl.url()).size(), 1); QCOMPARE(DUChain::self()->chainsForDocument(header.url()).size(), 1); QCOMPARE(DUChain::self()->chainsForDocument(impl.url()).size(), 1); } void TestDUChain::testReparseChangeEnvironment() { TestFile header("int foo() { return 42; }\n", "h"); TestFile impl("#include \"" + header.url().byteArray() + "\"\n" "int main() { return foo(); }", "cpp", &header); uint hashes[3] = {0, 0, 0}; for (int i = 0; i < 3; ++i) { impl.parse(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses|TopDUContext::AST|TopDUContext::ForceUpdate)); QVERIFY(impl.waitForParsed(5000)); { DUChainReadLocker lock; QVERIFY(impl.topContext()); auto env = dynamic_cast(impl.topContext()->parsingEnvironmentFile().data()); QVERIFY(env); QCOMPARE(env->environmentQuality(), ClangParsingEnvironment::Source); hashes[i] = env->environmentHash(); QVERIFY(hashes[i]); // we should never end up with multiple env files or chains in memory for these files QCOMPARE(DUChain::self()->allEnvironmentFiles(impl.url()).size(), 1); QCOMPARE(DUChain::self()->chainsForDocument(impl.url()).size(), 1); QCOMPARE(DUChain::self()->allEnvironmentFiles(header.url()).size(), 1); QCOMPARE(DUChain::self()->chainsForDocument(header.url()).size(), 1); } // in every run, we expect the environment to have changed for (int j = 0; j < i; ++j) { QVERIFY(hashes[i] != hashes[j]); } if (i == 0) { // 1) change defines m_provider->defines.insert("foooooooo", "baaar!"); } else if (i == 1) { // 2) change includes m_provider->includes.append(Path("/foo/bar/asdf/lalala")); } // 3) stop } } void TestDUChain::testMacrosRanges() { TestFile file("#define FUNC_MACROS(x) struct str##x{};\nFUNC_MACROS(x);", "cpp"); file.parse(TopDUContext::AllDeclarationsContextsAndUses); QVERIFY(file.waitForParsed(5000)); DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->localDeclarations().size(), 2); auto macroDefinition = file.topContext()->localDeclarations()[0]; QVERIFY(macroDefinition); QCOMPARE(macroDefinition->range(), RangeInRevision(0,8,0,19)); auto structDeclaration = file.topContext()->localDeclarations()[1]; QVERIFY(structDeclaration); QCOMPARE(structDeclaration->range(), RangeInRevision(1,0,1,0)); QCOMPARE(macroDefinition->uses().size(), 1); QCOMPARE(macroDefinition->uses().begin()->first(), RangeInRevision(1,0,1,11)); } void TestDUChain::testMultiLineMacroRanges() { TestFile file("#define FUNC_MACROS(x) struct str##x{};\nFUNC_MACROS(x\n);", "cpp"); file.parse(TopDUContext::AllDeclarationsContextsAndUses); QVERIFY(file.waitForParsed(5000)); DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->localDeclarations().size(), 2); auto macroDefinition = file.topContext()->localDeclarations()[0]; QVERIFY(macroDefinition); QCOMPARE(macroDefinition->range(), RangeInRevision(0,8,0,19)); auto structDeclaration = file.topContext()->localDeclarations()[1]; QVERIFY(structDeclaration); QCOMPARE(structDeclaration->range(), RangeInRevision(1,0,1,0)); QCOMPARE(macroDefinition->uses().size(), 1); QCOMPARE(macroDefinition->uses().begin()->first(), RangeInRevision(1,0,1,11)); } void TestDUChain::testNestedMacroRanges() { TestFile file("#define INNER int var; var = 0;\n#define MACRO() INNER\nint main(){MACRO(\n);}", "cpp"); file.parse(TopDUContext::AllDeclarationsContextsAndUses); QVERIFY(file.waitForParsed(5000)); DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->localDeclarations().size(), 3); auto main = file.topContext()->localDeclarations()[2]; QVERIFY(main); auto mainCtx = main->internalContext()->childContexts().first(); QVERIFY(mainCtx); QCOMPARE(mainCtx->localDeclarations().size(), 1); auto var = mainCtx->localDeclarations().first(); QVERIFY(var); QCOMPARE(var->range(), RangeInRevision(2,11,2,11)); QCOMPARE(var->uses().size(), 1); QCOMPARE(var->uses().begin()->first(), RangeInRevision(2,11,2,11)); } void TestDUChain::testNestedImports() { TestFile B("#pragma once\nint B();\n", "h"); TestFile C("#pragma once\n#include \"" + B.url().byteArray() + "\"\nint C();\n", "h"); TestFile A("#include \"" + B.url().byteArray() + "\"\n" + "#include \"" + C.url().byteArray() + "\"\nint A();\n", "cpp"); A.parse(); QVERIFY(A.waitForParsed(5000)); DUChainReadLocker lock; auto BCtx = DUChain::self()->chainForDocument(B.url().toUrl()); QVERIFY(BCtx); QVERIFY(BCtx->importedParentContexts().isEmpty()); auto CCtx = DUChain::self()->chainForDocument(C.url().toUrl()); QVERIFY(CCtx); QCOMPARE(CCtx->importedParentContexts().size(), 1); QVERIFY(CCtx->imports(BCtx, CursorInRevision(1, 10))); auto ACtx = A.topContext(); QVERIFY(ACtx); QCOMPARE(ACtx->importedParentContexts().size(), 2); QVERIFY(ACtx->imports(BCtx, CursorInRevision(0, 10))); QVERIFY(ACtx->imports(CCtx, CursorInRevision(1, 10))); } void TestDUChain::testEnvironmentWithDifferentOrderOfElements() { TestFile file("int main();\n", "cpp"); m_provider->includes.clear(); m_provider->includes.append(Path("/path1")); m_provider->includes.append(Path("/path2")); m_provider->defines.clear(); m_provider->defines.insert("key1", "value1"); m_provider->defines.insert("key2", "value2"); m_provider->defines.insert("key3", "value3"); uint previousHash = 0; for (int i: {0, 1, 2, 3}) { file.parse(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses|TopDUContext::AST|TopDUContext::ForceUpdate)); QVERIFY(file.waitForParsed(5000)); { DUChainReadLocker lock; QVERIFY(file.topContext()); auto env = dynamic_cast(file.topContext()->parsingEnvironmentFile().data()); QVERIFY(env); QCOMPARE(env->environmentQuality(), ClangParsingEnvironment::Source); if (previousHash) { if (i == 3) { QVERIFY(previousHash != env->environmentHash()); } else { QCOMPARE(previousHash, env->environmentHash()); } } previousHash = env->environmentHash(); QVERIFY(previousHash); } if (i == 0) { //Change order of defines. Hash of the environment should stay the same. m_provider->defines.clear(); m_provider->defines.insert("key3", "value3"); m_provider->defines.insert("key1", "value1"); m_provider->defines.insert("key2", "value2"); } else if (i == 1) { //Add the same macros twice. Hash of the environment should stay the same. m_provider->defines.clear(); m_provider->defines.insert("key2", "value2"); m_provider->defines.insert("key3", "value3"); m_provider->defines.insert("key3", "value3"); m_provider->defines.insert("key1", "value1"); } else if (i == 2) { //OTOH order of includes should change hash of the environment. m_provider->includes.clear(); m_provider->includes.append(Path("/path2")); m_provider->includes.append(Path("/path1")); } } } void TestDUChain::testReparseMacro() { TestFile file("#define DECLARE(a) typedef struct a##_ {} *a;\nDECLARE(D);\nD d;", "cpp"); file.parse(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses|TopDUContext::AST)); QVERIFY(file.waitForParsed(5000)); { DUChainReadLocker lock; QVERIFY(file.topContext()); } file.parse(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses|TopDUContext::AST|TopDUContext::ForceUpdate)); QVERIFY(file.waitForParsed(5000)); DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->localDeclarations().size(), 5); auto macroDefinition = file.topContext()->localDeclarations()[0]; QVERIFY(macroDefinition); QCOMPARE(macroDefinition->range(), RangeInRevision(0,8,0,15)); QCOMPARE(macroDefinition->uses().size(), 1); QCOMPARE(macroDefinition->uses().begin()->first(), RangeInRevision(1,0,1,7)); auto structDeclaration = file.topContext()->localDeclarations()[1]; QVERIFY(structDeclaration); QCOMPARE(structDeclaration->range(), RangeInRevision(1,0,1,0)); auto structTypedef = file.topContext()->localDeclarations()[3]; QVERIFY(structTypedef); QCOMPARE(structTypedef->range(), RangeInRevision(1,8,1,9)); QCOMPARE(structTypedef->uses().size(), 1); QCOMPARE(structTypedef->uses().begin()->first(), RangeInRevision(2,0,2,1)); } void TestDUChain::testGotoStatement() { TestFile file("int main() {\ngoto label;\ngoto label;\nlabel: return 0;}", "cpp"); file.parse(TopDUContext::AllDeclarationsContextsAndUses); QVERIFY(file.waitForParsed(5000)); DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->localDeclarations().size(), 1); auto main = file.topContext()->localDeclarations()[0]; QVERIFY(main); auto mainCtx = main->internalContext()->childContexts().first(); QVERIFY(mainCtx); QCOMPARE(mainCtx->localDeclarations().size(), 1); auto label = mainCtx->localDeclarations().first(); QVERIFY(label); QCOMPARE(label->range(), RangeInRevision(3,0,3,5)); QCOMPARE(label->uses().size(), 1); QCOMPARE(label->uses().begin()->first(), RangeInRevision(1,5,1,10)); QCOMPARE(label->uses().begin()->last(), RangeInRevision(2,5,2,10)); } void TestDUChain::testRangesOfOperatorsInsideMacro() { TestFile file("class Test{public: Test& operator++(int);};\n#define MACRO(var) var++;\nint main(){\nTest tst; MACRO(tst)}", "cpp"); file.parse(TopDUContext::AllDeclarationsContextsAndUses); QVERIFY(file.waitForParsed(5000)); DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->localDeclarations().size(), 3); auto testClass = file.topContext()->localDeclarations()[0]; QVERIFY(testClass); auto operatorPlusPlus = testClass->internalContext()->localDeclarations().first(); QVERIFY(operatorPlusPlus); QCOMPARE(operatorPlusPlus->uses().size(), 1); QCOMPARE(operatorPlusPlus->uses().begin()->first(), RangeInRevision(3,10,3,10)); } void TestDUChain::testUsesCreatedForDeclarations() { auto code = R"(template void functionTemplate(T); template void functionTemplate(U) {} namespace NS { class Class{}; } using NS::Class; Class function(); NS::Class function(){} int main () { functionTemplate(int()); function(); } )"; TestFile file(code, "cpp"); file.parse(TopDUContext::AllDeclarationsContextsAndUses); QVERIFY(file.waitForParsed()); DUChainReadLocker lock; QVERIFY(file.topContext()); auto functionTemplate = file.topContext()->findDeclarations(QualifiedIdentifier("functionTemplate")); QVERIFY(!functionTemplate.isEmpty()); auto functionTemplateDeclaration = DUChainUtils::declarationForDefinition(functionTemplate.first()); QVERIFY(!functionTemplateDeclaration->isDefinition()); #if CINDEX_VERSION_MINOR < 29 QEXPECT_FAIL("", "No API in LibClang to determine function template type", Continue); #endif QCOMPARE(functionTemplateDeclaration->uses().count(), 1); auto function = file.topContext()->findDeclarations(QualifiedIdentifier("function")); QVERIFY(!function.isEmpty()); auto functionDeclaration = DUChainUtils::declarationForDefinition(function.first()); QVERIFY(!functionDeclaration->isDefinition()); QCOMPARE(functionDeclaration->uses().count(), 1); } - void TestDUChain::testReparseIncludeGuard() { TestFile header("#ifndef GUARD\n#define GUARD\nint something;\n#endif\n", "h"); TestFile impl("#include \"" + header.url().byteArray() + "\"\n", "cpp", &header); impl.parseAndWait(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::AST )); { DUChainReadLocker lock; QCOMPARE(static_cast(impl.topContext()-> importedParentContexts().first().context(impl.topContext()))->problems().size(), 0); } impl.parseAndWait(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::ForceUpdateRecursive)); { DUChainReadLocker lock; QCOMPARE(static_cast(impl.topContext()-> importedParentContexts().first().context(impl.topContext()))->problems().size(), 0); } } + +void TestDUChain::testExternC() +{ + auto code = R"(extern "C" { void foo(); })"; + TestFile file(code, "cpp"); + file.parse(TopDUContext::AllDeclarationsContextsAndUses); + QVERIFY(file.waitForParsed()); + + DUChainReadLocker lock; + auto top = file.topContext(); + QVERIFY(top); + QVERIFY(!top->findDeclarations(QualifiedIdentifier("foo")).isEmpty()); +} + +void TestDUChain::testReparseUnchanged_data() +{ + QTest::addColumn("headerCode"); + QTest::addColumn("implCode"); + + QTest::newRow("include-guards") << R"( + #ifndef GUARD + #define GUARD + int something; + #endif + )" << R"( + #include "%1" + )"; + + QTest::newRow("template-default-parameters") << R"( + #ifndef TEST_H + #define TEST_H + + template + class dummy; + + template + class dummy { + int field[T]; + }; + + #endif + )" << R"( + #include "%1" + + int main(int, char **) { + dummy<> x; + (void)x; + } + )"; +} + +void TestDUChain::testReparseUnchanged() +{ + QFETCH(QString, headerCode); + QFETCH(QString, implCode); + TestFile header(headerCode, "h"); + TestFile impl(implCode.arg(header.url().str()), "cpp", &header); + + auto checkProblems = [&] (bool reparsed) { + DUChainReadLocker lock; + auto headerCtx = DUChain::self()->chainForDocument(header.url()); + QVERIFY(headerCtx); + QVERIFY(headerCtx->problems().isEmpty()); + auto implCtx = DUChain::self()->chainForDocument(impl.url()); + QVERIFY(implCtx); + if (reparsed) { + QEXPECT_FAIL("template-default-parameters", "the precompiled preamble messes the default template paramters up", Continue); + } + QVERIFY(implCtx->problems().isEmpty()); + }; + + impl.parseAndWait(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::AST )); + checkProblems(false); + + impl.parseAndWait(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::ForceUpdateRecursive)); + checkProblems(true); +} diff --git a/languages/clang/tests/test_duchain.h b/languages/clang/tests/test_duchain.h index 90bbe36833..4873152962 100644 --- a/languages/clang/tests/test_duchain.h +++ b/languages/clang/tests/test_duchain.h @@ -1,85 +1,88 @@ /* * Copyright 2014 Milian Wolff * * 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 DUCHAINTEST_H #define DUCHAINTEST_H #include class TestEnvironmentProvider; class TestDUChain : public QObject { Q_OBJECT public: ~TestDUChain(); private slots: void initTestCase(); void cleanupTestCase(); void init(); void cleanup(); void testComments(); void testComments_data(); void testElaboratedType(); void testElaboratedType_data(); void testInclude(); void testIncludeLocking(); void testReparse(); void testReparseError(); void testTemplate(); void testNamespace(); void testAutoTypeDeduction(); void testTypeDeductionInTemplateInstantiation(); void testVirtualMemberFunction(); void testBaseClasses(); void testReparseBaseClasses(); void testReparseBaseClassesTemplates(); void testGlobalFunctionDeclaration(); void testFunctionDefinitionVsDeclaration(); void testEnsureNoDoubleVisit(); void testReparseWithAllDeclarationsContextsAndUses(); void testReparseOnDocumentActivated(); void testParsingEnvironment(); void testSystemIncludes(); void testReparseInclude(); void testReparseChangeEnvironment(); void testMacrosRanges(); void testNestedImports(); void testEnvironmentWithDifferentOrderOfElements(); void testReparseMacro(); void testMultiLineMacroRanges(); void testNestedMacroRanges(); void testGotoStatement(); void testRangesOfOperatorsInsideMacro(); void testActiveDocumentHasASTAttached(); void testUsesCreatedForDeclarations(); void testReparseIncludeGuard(); + void testExternC(); + void testReparseUnchanged_data(); + void testReparseUnchanged(); void benchDUChainBuilder(); private: QScopedPointer m_provider; }; #endif // DUCHAINTEST_H diff --git a/languages/cpp/codecompletion/model.cpp b/languages/cpp/codecompletion/model.cpp index eeb1c0dc19..052923e72c 100644 --- a/languages/cpp/codecompletion/model.cpp +++ b/languages/cpp/codecompletion/model.cpp @@ -1,250 +1,251 @@ /* * KDevelop C++ Code Completion Support * * Copyright 2006-2007 Hamish Rodda * Copyright 2007-2008 David Nolden * * 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 "model.h" #include #include #include #include #include #include #include #include #include "worker.h" #include "context.h" #include "../cppduchain/typeconversion.h" #include "../debug.h" using namespace KTextEditor; using namespace KDevelop; namespace Cpp { CodeCompletionModel* CodeCompletionModel::s_self = nullptr; bool useArgumentHintInAutomaticCompletion() { return false; } CodeCompletionModel::CodeCompletionModel( QObject * parent ) : KDevelop::CodeCompletionModel(parent) { setForceWaitForModel(true); Q_ASSERT(!s_self); s_self = this; connect(ICore::self()->languageController()->backgroundParser(), &BackgroundParser::parseJobFinished, this, &CodeCompletionModel::parseJobFinished); } CodeCompletionModel::~CodeCompletionModel() { s_self = nullptr; } CodeCompletionModel* CodeCompletionModel::self() { return s_self; } void CodeCompletionModel::startCompletionAfterParsing(const IndexedString& path) { m_awaitDocument = path; } void CodeCompletionModel::parseJobFinished(ParseJob* job) { if (job->document() != m_awaitDocument || ICore::self()->languageController()->backgroundParser()->isQueued(m_awaitDocument)) { return; } IDocument* doc = ICore::self()->documentController()->documentForUrl(m_awaitDocument.toUrl()); m_awaitDocument = {}; if (!doc) { return; } auto view = doc->activeTextView(); if (!view || !view->hasFocus()) { return; } auto iface = dynamic_cast(view); if (iface) { ///@todo 1. This is a non-public interface, and 2. Completion should be started in "automatic invocation" mode QMetaObject::invokeMethod(view, "userInvokedCompletion"); } } bool CodeCompletionModel::shouldStartCompletion(KTextEditor::View* view, const QString& inserted, bool userInsertion, const KTextEditor::Cursor& position) { qCDebug(CPP) << inserted; QString insertedTrimmed = inserted.trimmed(); TypeConversion::startCache(); QString lineText = view->document()->text(KTextEditor::Range(position.line(), 0, position.line(), position.column())); if(lineText.startsWith("#") && lineText.contains("include") && inserted.endsWith("/")) return true; //Directory-content completion if(insertedTrimmed.endsWith('\"')) return false; //Never start completion behind a string literal if(useArgumentHintInAutomaticCompletion()) if(insertedTrimmed.endsWith( '(' ) || insertedTrimmed.endsWith(',') || insertedTrimmed.endsWith('<') || insertedTrimmed.endsWith(":") ) return true; //Start automatic completion behind '::' if(insertedTrimmed.endsWith(":") && position.column() > 1 && lineText.right(2) == "::") return true; return KDevelop::CodeCompletionModel::shouldStartCompletion(view, inserted, userInsertion, position); } void CodeCompletionModel::aborted(KTextEditor::View* view) { qCDebug(CPP) << "aborting"; worker()->abortCurrentCompletion(); TypeConversion::stopCache(); KDevelop::CodeCompletionModel::aborted(view); } bool isValidIncludeDirectiveCharacter(QChar character) { return character.isLetterOrNumber() || character == '_' || character == '-' || character == '.'; } bool CodeCompletionModel::shouldAbortCompletion(KTextEditor::View* view, const KTextEditor::Range& range, const QString& currentCompletion) { if(view->cursorPosition() < range.start() || view->cursorPosition() > range.end()) return true; //Always abort when the completion-range has been left //Do not abort completions when the text has been empty already before and a newline has been entered QString line = view->document()->line(range.start().line()).trimmed(); if(line.startsWith("#include")) { //Do custom check for include directives, since we allow more character then during usual completion QString text = view->document()->text(range); for(int a = 0; a < text.length(); ++a) { if(!isValidIncludeDirectiveCharacter(text[a])) return true; } return false; } static const QRegExp allowedText("^\\~?(\\w*)"); return !allowedText.exactMatch(currentCompletion); } KDevelop::CodeCompletionWorker* CodeCompletionModel::createCompletionWorker() { return new CodeCompletionWorker(this); } Range CodeCompletionModel::updateCompletionRange(View* view, const KTextEditor::Range& range) { if(completionContext()) { Cpp::CodeCompletionContext* cppContext = dynamic_cast(completionContext().data()); Q_ASSERT(cppContext); cppContext->setFollowingText(view->document()->text(range)); bool didReset = false; if(completionContext()->ungroupedElements().size()) { //Update the ungrouped elements, since they may have changed their text int row = rowCount() - completionContext()->ungroupedElements().size(); foreach(KDevelop::CompletionTreeElementPointer item, completionContext()->ungroupedElements()) { KDevelop::CompletionCustomGroupNode* group = dynamic_cast(item.data()); if(group) { int subRow = 0; foreach(KDevelop::CompletionTreeElementPointer item, group->children) { if(item->asItem() && item->asItem()->dataChangedWithInput()) { // dataChanged(index(subRow, Name, parent), index(subRow, Name, parent)); qCDebug(CPP) << "doing dataChanged"; ///@todo This is very expensive, but kate doesn't listen for dataChanged(..). Find a cheaper way to achieve this. beginResetModel(); endResetModel(); didReset = true; break; } ++subRow; } } if(didReset) break; if(item->asItem() && item->asItem()->dataChangedWithInput()) { beginResetModel(); endResetModel(); didReset = true; break; } ++row; } // dataChanged(index(rowCount() - completionContext()->ungroupedElements().size(), 0), index(rowCount()-1, columnCount()-1 )); } } QString line = view->document()->line(range.start().line()).trimmed(); if(line.startsWith("#include")) { //Skip over all characters that are allowed in a filename but usually not in code-completion KTextEditor::Range newRange = range; while(newRange.start().column() > 0) { KTextEditor::Cursor newStart = newRange.start(); newStart.setColumn(newStart.column()-1); QChar character = view->document()->characterAt(newStart); if(isValidIncludeDirectiveCharacter(character)) { newRange.setStart(newStart); //Skip }else{ break; } } qCDebug(CPP) << "new range:" << newRange; return newRange; } return KDevelop::CodeCompletionModel::updateCompletionRange(view, range); } Range CodeCompletionModel::completionRange(View* view, const KTextEditor::Cursor& position) { Range range = KDevelop::CodeCompletionModel::completionRange(view, position); if (range.start().column() > 0) { KTextEditor::Range preRange(Cursor(range.start().line(), range.start().column() - 1), Cursor(range.start().line(), range.start().column())); const QString contents = view->document()->text(preRange); if ( contents == "~" ) { range.expandToRange(preRange); } } return range; } -void CodeCompletionModel::foundDeclarations(QList item, KDevelop::CodeCompletionContext::Ptr completionContext) +void CodeCompletionModel::foundDeclarations(const QList& item, + const KDevelop::CodeCompletionContext::Ptr& completionContext) { TypeConversion::startCache(); KDevelop::CodeCompletionModel::foundDeclarations(item, completionContext); } } diff --git a/languages/cpp/codecompletion/model.h b/languages/cpp/codecompletion/model.h index 2d86c78843..ecd0c5059d 100644 --- a/languages/cpp/codecompletion/model.h +++ b/languages/cpp/codecompletion/model.h @@ -1,79 +1,80 @@ /* * KDevelop C++ Code Completion Support * * Copyright 2006-2007 Hamish Rodda * Copyright 2007-2008 David Nolden * * 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. */ #ifndef KDEVCPPCODECOMPLETIONMODEL_H #define KDEVCPPCODECOMPLETIONMODEL_H #include #include #include #include namespace KDevelop { class DUContext; class Declaration; class CodeCompletionContext; class CompletionTreeElement; class ParseJob; } namespace Cpp { class NavigationWidget; class CodeCompletionWorker; bool useArgumentHintInAutomaticCompletion(); class CodeCompletionModel : public KDevelop::CodeCompletionModel { Q_OBJECT public: CodeCompletionModel(QObject* parent); virtual ~CodeCompletionModel(); static CodeCompletionModel* self(); void startCompletionAfterParsing(const KDevelop::IndexedString& path); protected: virtual KTextEditor::Range completionRange(KTextEditor::View* view, const KTextEditor::Cursor& position) override; virtual KTextEditor::Range updateCompletionRange(KTextEditor::View* view, const KTextEditor::Range& range) override; virtual void aborted(KTextEditor::View* view) override; virtual bool shouldAbortCompletion (KTextEditor::View* view, const KTextEditor::Range& range, const QString& currentCompletion) override; virtual bool shouldStartCompletion (KTextEditor::View*, const QString&, bool userInsertion, const KTextEditor::Cursor&) override; virtual KDevelop::CodeCompletionWorker* createCompletionWorker() override; - virtual void foundDeclarations(QList< QExplicitlySharedDataPointer< KDevelop::CompletionTreeElement > > item, QExplicitlySharedDataPointer< KDevelop::CodeCompletionContext > completionContext) override; + virtual void foundDeclarations(const QList>& item, + const QExplicitlySharedDataPointer& completionContext) override; private slots: void parseJobFinished(KDevelop::ParseJob* job); private: static CodeCompletionModel* s_self; // when a document with this path has finished its parse job, code-completion will be restarted KDevelop::IndexedString m_awaitDocument; }; } #endif diff --git a/languages/cpp/cppduchain/cppducontext.h b/languages/cpp/cppduchain/cppducontext.h index 9238d9b945..2cb8c5f6c6 100644 --- a/languages/cpp/cppduchain/cppducontext.h +++ b/languages/cpp/cppduchain/cppducontext.h @@ -1,763 +1,764 @@ /* This file is part of KDevelop Copyright 2007 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /* Some mindmapping about how the template-system works: While construction: - Simplify: Template-parameters are types - Within template-contexts, do not resolve any types. Instead create "virtual types" that will resolve the types when template-parameters are given. (DelayedType) - ready Later: - Searching instantiated template-class: - return virtual declaration - return virtual context (Change template-parameter-context to given template-arguments) - Searching IN instantiated template-class: - When searching local declarations: - Check whether they are already in the instantiated context, if yes return them - If not, Search in non-instantiated context(only for local declarations), then: - Copy & Change returned objects: - Resolve virtual types (DelayedType) - Change parent-context to virtual context - Change internal context, (create virtual, move set parent) - How template-parameters are resolved: - The DUContext's with type DUContext::Template get their template-parameter declarations instantiated and added locally. Then they will be found when resolving virtual types. - */ #ifndef CPPDUCONTEXT_H #define CPPDUCONTEXT_H #include #include #include #include #include #include #include #include #include #include "typeutils.h" #include "templatedeclaration.h" #include "expressionevaluationresult.h" #include "cppduchain.h" #include "cpptypes.h" #include "templatedeclaration.h" #include "cppdebughelper.h" using namespace KDevelop; namespace Cpp { extern QMutex cppDuContextInstantiationsMutex; ///This class breaks up the logic of searching a declaration in C++, so QualifiedIdentifiers as well as AST-based lookup mechanisms can be used for searching class FindDeclaration { public: FindDeclaration( const DUContext* ctx, const TopDUContext* source, DUContext::SearchFlags flags, const CursorInRevision& position, AbstractType::Ptr dataType = AbstractType::Ptr() ) : m_context(ctx), m_source(source), m_flags(flags), m_position(position), m_dataType(dataType) { Q_ASSERT(m_source); } void openQualifiedIdentifier( bool isExplicitlyGlobal ) { StatePtr s(new State); s->identifier.setExplicitlyGlobal( isExplicitlyGlobal ); m_states << s; } ///Can be used to just append a result that was computed outside. closeQualifiedIdentifier(...) must still be called. void openQualifiedIdentifier( const ExpressionEvaluationResult& result ) { StatePtr s(new State); s->expressionResult = result; s->result.clear(); foreach(const DeclarationId& decl, result.allDeclarations) s->result << DeclarationPointer( decl.getDeclaration(const_cast(topContext())) ); m_states << s; } /** * After this was called, lastDeclarations(..) can be used to retrieve declarations of the qualified identifier. * The DUChain needs to be locked when this is called. * */ void closeQualifiedIdentifier(); /** * The identifier must not have template identifiers, those need to be added using openQualifiedIdentifier(..) and closeQualifiedIdentifier(..) * */ void openIdentifier( const Identifier& identifier ) { m_states.top()->identifier.push(identifier); } /** * When closeIdentifier() is called, the last opened identifier is searched, and can be retrieved using lastDeclarations(). * Returns false when the search should be stopped. * The DUChain needs to be locked when this is called. * @param isFinalIdentifier Whether the closed identifier the last one. Needed to know when to apply what search-flags. * */ bool closeIdentifier(bool isFinalIdentifier); ///For debugging. Describes the last search context. QString describeLastContext() const { if( m_lastScopeContext ) { return "Context " + m_lastScopeContext->scopeIdentifier(true).toString() + QString(" from %1:%2").arg(m_lastScopeContext->url().str()).arg(m_lastScopeContext->range().start.line); } else { return QString("Global search with top-context %2").arg(topContext()->url().str()); } } /** * Returns the Declarations found for the last closed qualified identifier. * * */ QList lastDeclarations() const { return m_lastDeclarations; } const TopDUContext* topContext() const { return m_source; } uint currentLength() const { return m_states.size(); } private: ///Uses the instantiation-information from the context of decl as parent of templateArguments. Declaration* instantiateDeclaration( Declaration* decl, const InstantiationInformation& templateArguments ) const; struct State : public QSharedData { State() { } QualifiedIdentifier identifier; //identifier including eventual namespace prefix InstantiationInformation templateParameters; ///One of the following is filled QList result; ExpressionEvaluationResult expressionResult; }; typedef QExplicitlySharedDataPointer StatePtr; QStack m_states; const DUContext* m_context; const TopDUContext* m_source; DUContext::SearchFlags m_flags; QList m_lastDeclarations; CursorInRevision m_position; AbstractType::Ptr m_dataType; DUContextPointer m_lastScopeContext; //For debugging, last context in which we searched }; /** * This is a du-context template that wraps the c++-specific logic around existing DUContext-derived classes. * In practice this means DUContext and TopDUContext. * */ template class CppDUContext : public BaseContext { public: template CppDUContext(Data& data) : BaseContext(data), m_instantiatedFrom(0) { } ///Parameters will be reached to the base-class template CppDUContext( const Param1& p1, const Param2& p2, bool isInstantiationContext ) : BaseContext(p1, p2, isInstantiationContext), m_instantiatedFrom(0) { static_cast(this)->d_func_dynamic()->setClassId(this); } ///Both parameters will be reached to the base-class. This fits TopDUContext. template CppDUContext( const Param1& p1, const Param2& p2, const Param3& p3) : BaseContext(p1, p2, p3), m_instantiatedFrom(0) { static_cast(this)->d_func_dynamic()->setClassId(this); } template CppDUContext( const Param1& p1, const Param2& p2) : BaseContext(p1, p2), m_instantiatedFrom(0) { static_cast(this)->d_func_dynamic()->setClassId(this); } ///Matches the qualified identifier represented by the search item to the tail of the context's scope identfier ///Also matches searches without template-parameters to fully instantiated contexts ///Returns true if they match inline bool matchSearchItem(DUContext::SearchItem::Ptr item, const DUContext* ctx) const { DUContext::SearchItem::PtrList items; while(1) { items << item; if(!item->next.isEmpty()) item = item->next[0]; else break; } while(ctx && !items.isEmpty()) { QualifiedIdentifier localId = ctx->localScopeIdentifier(); if(localId.isEmpty()) return false; int matchPos = localId.count()-1; while(!items.isEmpty() && matchPos >= 0) { const auto id = items.back()->identifier.identifier(); if(id.templateIdentifiersCount()) return false; //Don't match when there is template parameters, as that needs other mechanisms if(id == localId.at(matchPos) || id.identifier() == localId.at(matchPos).identifier()) { --matchPos; items.resize(items.size()-1); }else{ return false; } } if(items.isEmpty()) return true; ctx = ctx->parentContext(); } return false; } ///Overridden to take care of templates and other c++ specific things virtual bool findDeclarationsInternal(const DUContext::SearchItem::PtrList& identifiers, const CursorInRevision& position, const AbstractType::Ptr& dataType, DUContext::DeclarationList& ret, const TopDUContext* source, typename BaseContext::SearchFlags basicFlags, uint depth ) const override { if(this->type() == DUContext::Class && identifiers.count() == 1 && !(basicFlags & DUContext::NoSelfLookUp) && !(basicFlags & DUContext::OnlyFunctions) && !this->indexedLocalScopeIdentifier().isEmpty() && !identifiers[0]->isExplicitlyGlobal) { //Check whether we're searching for just the name of this context's class. If yes, return this context's owner. if(matchSearchItem(identifiers[0], this)) { Declaration* owner = this->owner(); if(owner) { if(basicFlags & DUContext::NoUndefinedTemplateParams) { //If no undefined template parameters are allowed, make sure this template has all parameters assigned. TemplateDeclaration* templateOwner = dynamic_cast(this->owner()); if(templateOwner) { if(!templateOwner->instantiatedFrom()) return false; DUContext* templateContext = templateOwner->templateContext(source); if(templateContext) { foreach(Declaration* decl, templateContext->localDeclarations()) { if(decl->type()) { return false; } } } } } ret << this->owner(); return true; } } } if( basicFlags & BaseContext::DirectQualifiedLookup ) { //ifDebug( qDebug() << "redirecting findDeclarationsInternal in " << this << "(" << this->scopeIdentifier() <<") for \"" << identifier.toString() << "\""; ) //We use DirectQualifiedLookup to signal that we don't need to do the whole scope-search, template-resolution etc. logic. return BaseContext::findDeclarationsInternal(identifiers, position, dataType, ret, source, basicFlags, depth ); } for ( const DUContext::SearchItem::Ptr& item : identifiers ) foreach( const QualifiedIdentifier& id, item->toList() ) if(!findDeclarationsInternal(id, position, dataType, ret, source, basicFlags)) return false; // Remove all forward-declarations if there is a real declaration in the list bool haveForwardDeclaration = false; bool haveNonForwardDeclaration = false; for(int a = 0; a < ret.size(); ++a) { if(ret[a]->isForwardDeclaration()) haveForwardDeclaration = true; else haveNonForwardDeclaration = true; } if(haveForwardDeclaration && haveNonForwardDeclaration) { DUContext::DeclarationList oldRet = ret; ret.clear(); for(int a = 0; a < oldRet.size(); ++a) if(!oldRet[a]->isForwardDeclaration()) ret.append(oldRet[a]); } return true; } bool findDeclarationsInternal(const QualifiedIdentifier& identifier, const CursorInRevision& position, const AbstractType::Ptr& dataType, DUContext::DeclarationList& ret, const TopDUContext* source, typename BaseContext::SearchFlags basicFlags) const { ifDebug( qDebug() << "findDeclarationsInternal in " << this << "(" << this->scopeIdentifier() <<") for \"" << identifier.toString() << "\""; ) FindDeclaration find( this, source, basicFlags, position, dataType ); find.openQualifiedIdentifier( identifier.explicitlyGlobal() ); int idCount = identifier.count(); for( int num = 0; num < idCount; num++ ) { { Identifier current = identifier.at(num); current.clearTemplateIdentifiers(); find.openIdentifier(current); } { Identifier currentIdentifier = identifier.at(num); ///Step 1: Resolve the template-arguments //Since there may be non-type template-parameters, represent them as ExpressionEvaluationResult's int tempCount = currentIdentifier.templateIdentifiersCount(); for( int a = 0; a < tempCount; a++ ) { //Use the already available mechanism for resolving delayed types Cpp::ExpressionEvaluationResult res; IndexedTypeIdentifier i = currentIdentifier.templateIdentifier(a); //If the identifier is empty, it is probably just a mark that a template should be instantiated, but without explicit paremeters. QualifiedIdentifier qid(i.identifier().identifier()); if( !qid.isEmpty() ) { DelayedType::Ptr delayed( new DelayedType() ); delayed->setIdentifier( i ); res.type = Cpp::resolveDelayedTypes( delayed.cast(), this, source, basicFlags & KDevelop::DUContext::NoUndefinedTemplateParams ? DUContext::NoUndefinedTemplateParams : DUContext::NoSearchFlags )->indexed(); if( basicFlags & KDevelop::DUContext::NoUndefinedTemplateParams) { AbstractType::Ptr targetTypePtr = TypeUtils::unAliasedType(TypeUtils::targetType(res.type.abstractType(), 0)); if (targetTypePtr.cast() || (targetTypePtr.cast() && targetTypePtr.cast()->kind() == DelayedType::Delayed)) { ifDebug( qDebug() << "stopping because the type" << targetTypePtr->toString() << "of" << i.toString() << "is bad"; ) return false; } } ifDebug( if( !res.isValid() ) qDebug() << "Could not resolve template-parameter \"" << currentIdentifier.templateIdentifier(a).toString() << "\" in \"" << identifier.toString() << "resolved:" << res.toString(); ) } find.openQualifiedIdentifier( res ); find.closeQualifiedIdentifier(); } } if( !find.closeIdentifier( num == idCount-1 ) ) return false; } find.closeQualifiedIdentifier(); foreach( const DeclarationPointer& decl, find.lastDeclarations() ) ret.append(decl.data()); return true; } virtual void findLocalDeclarationsInternal( const IndexedIdentifier& identifier, const CursorInRevision & position, const AbstractType::Ptr& dataType, DUContext::DeclarationList& ret, const TopDUContext* source, typename BaseContext::SearchFlags flags ) const override { ifDebug( qDebug() << "findLocalDeclarationsInternal in " << this << "with parent" << this->parentContext() << "(" << this->scopeIdentifier() <<") for \"" << identifier.identifier() << "\""; ) ifDebug( if( BaseContext::owner() && BaseContext::owner() ) qDebug() << "in declaration: " << "(" << BaseContext::owner()->toString(); ) /** - When searching local declarations: - Check whether they are already in the instantiated context, if yes return them - If not, Search in non-instantiated context(only for local declarations), then: - Copy & Change returned objects: - Resolve virtual types (DelayedType) - Change parent-context to virtual context - Change internal context, (create virtual, move set parent) * */ int retCount = ret.size(); BaseContext::findLocalDeclarationsInternal(identifier, position, dataType, ret, source, flags ); ifDebug( qDebug() << "basically found:" << ret.count() - retCount << "containing" << BaseContext::localDeclarations().count() << "searching-position" << position.castToSimpleCursor(); ) if( !(flags & DUContext::NoFiltering) ) { //Filter out constructors and if needed unresolved template-params for(int a = 0; a < ret.size(); ) { AbstractType::Ptr retAbstractTypePtr = ret[a]->abstractType(); if( ( (flags & KDevelop::DUContext::NoUndefinedTemplateParams) && retAbstractTypePtr.cast() ) || ( (!(flags & BaseContext::OnlyFunctions)) && (dynamic_cast(ret[a]) && static_cast(ret[a])->isConstructor() ) ) ) { //Maybe this filtering should be done in the du-chain? ret.removeAt(a); //qDebug() << "filtered out 1 declaration"; } else { ++a; } } } ifDebug( if( BaseContext::owner() && BaseContext::owner() ) qDebug() << "in declaration: " << "(" << BaseContext::owner()->toString(); ) ifDebug( qDebug() << "instantiated from:" << m_instantiatedFrom; ) if( m_instantiatedFrom && ret.size() == retCount ) { ///Search in the context this one was instantiated from DUContext::DeclarationList decls; ifDebug( qDebug() << "searching base"; ) m_instantiatedFrom->findLocalDeclarationsInternal( identifier, position, dataType, decls, source, flags ); ifDebug( if( BaseContext::owner() && BaseContext::owner() ) qDebug() << "in declaration: " << "(" << BaseContext::owner()->toString(); ) ifDebug( qDebug() << "found" << decls.count() << "in base"; ) InstantiationInformation memberInstantiationInformation; memberInstantiationInformation.previousInstantiationInformation = m_instantiatedWith; for ( Declaration* decl : decls ) { TemplateDeclaration* templateDecl = dynamic_cast(decl); if(!templateDecl) { qDebug() << "problem"; } else { Declaration* copy; DUContext* current = decl->context(); while(current != m_instantiatedFrom && current) { // The declaration has been propagated up from a sub-context like an enumerator, add more empty instantiation information // so the depth is matched correctly by the information InstantiationInformation i; i.previousInstantiationInformation = memberInstantiationInformation.indexed(); memberInstantiationInformation = i; current = current->parentContext(); } Q_ASSERT(source); copy = templateDecl->instantiate(memberInstantiationInformation, source); //This can happen in case of explicit specializations // if(copy->context() != this) // qCWarning(CPPDUCHAIN) << "serious problem: Instatiation is in wrong context, should be in this one"; if(copy) ret.append(copy); } } } } virtual void visit(DUChainVisitor& visitor) override { QMutexLocker l(&cppDuContextInstantiationsMutex); foreach(CppDUContext* ctx, m_instatiations) ctx->visit(visitor); BaseContext::visit(visitor); } virtual void deleteUses() override { QMutexLocker l(&cppDuContextInstantiationsMutex); foreach(CppDUContext* ctx, m_instatiations) ctx->deleteUses(); BaseContext::deleteUses(); } virtual bool foundEnough( const DUContext::DeclarationList& decls, DUContext::SearchFlags flags ) const override { if(flags & DUContext::NoFiltering) return false; if( decls.isEmpty() ) return false; if( dynamic_cast(decls[0]) && BaseContext::type() != DUContext::Class ) //In classes, one function hides all overloads return false; //Collect overloaded function-declarations return true; } /** * Set the context which this is instantiated from. This context will be strictly attached to that context, and will be deleted once the other is deleted. * */ void setInstantiatedFrom( CppDUContext* context, const InstantiationInformation& templateArguments ) { Q_ASSERT(!dynamic_cast(this)); if(context && context->m_instantiatedFrom) { setInstantiatedFrom(context->m_instantiatedFrom, templateArguments); return; } QMutexLocker l(&cppDuContextInstantiationsMutex); if( m_instantiatedFrom ) { Q_ASSERT(m_instantiatedFrom->m_instatiations[m_instantiatedWith] == this); m_instantiatedFrom->m_instatiations.remove( m_instantiatedWith ); } m_instantiatedWith = templateArguments.indexed(); if(context) { //Change the identifier so it contains the template-parameters QualifiedIdentifier totalId = this->localScopeIdentifier(); KDevelop::Identifier id; if( !totalId.isEmpty() ) { id = totalId.last(); totalId.pop(); } id.clearTemplateIdentifiers(); FOREACH_FUNCTION(const IndexedType& arg, templateArguments.templateParameters) { AbstractType::Ptr type(arg.abstractType()); IdentifiedType* identified = dynamic_cast(type.data()); if(identified) id.appendTemplateIdentifier( IndexedTypeIdentifier(identified->qualifiedIdentifier()) ); else if(type) id.appendTemplateIdentifier( IndexedTypeIdentifier(type->toString(), true) ); else id.appendTemplateIdentifier( IndexedTypeIdentifier("no type") ); } totalId.push(id); this->setLocalScopeIdentifier(totalId); } m_instantiatedFrom = context; Q_ASSERT(m_instantiatedFrom != this); if(m_instantiatedFrom) { if(!m_instantiatedFrom->m_instatiations.contains(m_instantiatedWith)) { m_instantiatedFrom->m_instatiations.insert( m_instantiatedWith, this ); }else{ qDebug() << "created orphaned instantiation for" << context->m_instatiations[m_instantiatedWith]->scopeIdentifier(true).toString(); m_instantiatedFrom = 0; } } } virtual void applyUpwardsAliases(DUContext::SearchItem::PtrList& identifiers, const TopDUContext* source) const override { BaseContext::applyUpwardsAliases(identifiers, source); ///@see Iso C++ 3.4.1 : Unqualified name lookup: ///We need to make sure that when leaving a function definition, the namespace components are searched DUContext::ContextType type = BaseContext::type(); if(type == DUContext::Function || type == DUContext::Other || type == DUContext::Helper) { QualifiedIdentifier prefix = BaseContext::localScopeIdentifier(); if(prefix.count() > 1) { prefix = Cpp::namespaceScopeComponentFromContext(prefix, this, source); if(!prefix.isEmpty()) { prefix.setExplicitlyGlobal(true); DUContext::SearchItem::Ptr newItem(new DUContext::SearchItem(prefix)); newItem->addToEachNode(identifiers); if(!newItem->next.isEmpty()) //Can happen if the identifier was explicitly global identifiers.insert(0, newItem); } } } } /** * If this returns nonzero value, this context is a instatiation of some other context, and that other context will be returned here. * */ CppDUContext* instantiatedFrom() const { return m_instantiatedFrom; } virtual bool inDUChain() const override { ///There must be no changes from the moment m_instantiatedFrom is set, because then it can be found as an instantiation by other threads return m_instantiatedFrom || BaseContext::inDUChain(); } IndexedInstantiationInformation instantiatedWith() const { return m_instantiatedWith; } virtual bool shouldSearchInParent(typename BaseContext::SearchFlags flags) const override { //If the parent context is a class context, we should even search it from an import return (BaseContext::parentContext() && BaseContext::parentContext()->type() == DUContext::Class) || BaseContext::shouldSearchInParent(flags); } virtual DUContext* specialize(const IndexedInstantiationInformation& specialization, const TopDUContext* topContext, int upDistance) override { if(specialization.index() == 0) return this; else { InstantiationInformation information = specialization.information(); //Add empty elements until the specified depth for(int a = 0; a < upDistance; ++a) { InstantiationInformation nextInformation; nextInformation.previousInstantiationInformation = information.indexed(); information = nextInformation; } return instantiate(information, topContext); } } ///@see TemplateDeclaration::instantiate - DUContext* instantiate(const InstantiationInformation& info, const TopDUContext* source) { - if(!info.isValid() || m_instantiatedWith == info.indexed() || !this->parentContext()) + DUContext* instantiate(const InstantiationInformation& info, const TopDUContext* source, const int depth=0) { + const int maxDepth = 50; + if(!info.isValid() || m_instantiatedWith == info.indexed() || !this->parentContext() ||depth > maxDepth) return this; if(m_instantiatedFrom) return m_instantiatedFrom->instantiate(info, source); { typename QHash* >::const_iterator it = m_instatiations.constFind(info.indexed()); if(it != m_instatiations.constEnd()) return *it; } if(this->owner()) { TemplateDeclaration* templ = dynamic_cast(this->owner()); if(templ) { Declaration* instantiatedDecl = templ->instantiate(info, source); if(!instantiatedDecl) return 0; return instantiatedDecl->internalContext(); } } DUContext* surroundingContext = this->parentContext(); Q_ASSERT(surroundingContext); { //This context does not have an attached declaration, but it needs to be instantiated. CppDUContext* parent = dynamic_cast* >(this->parentContext()); if(parent) - surroundingContext = parent->instantiate(info.previousInstantiationInformation.information(), source); + surroundingContext = parent->instantiate(info.previousInstantiationInformation.information(), source, depth+1); } if(!surroundingContext) return 0; return instantiateDeclarationAndContext( surroundingContext, source, this, info, 0, 0 ); } virtual QWidget* createNavigationWidget(Declaration* decl, TopDUContext* topContext, const QString& htmlPrefix, const QString& htmlSuffix) const override; enum { Identity = BaseContext::Identity + 50 }; ///Duchain must be write-locked void deleteAllInstantiations() { //Specializations will be destroyed the same time this is destroyed CppDUContext* oldFirst = 0; QMutexLocker l(&cppDuContextInstantiationsMutex); while(!m_instatiations.isEmpty()) { CppDUContext* first = 0; first = *m_instatiations.begin(); Q_ASSERT(first != oldFirst); Q_UNUSED(oldFirst); oldFirst = first; l.unlock(); ///TODO: anonymous contexts should get deleted but that is crashy /// see also declarationbuilder which also encountered this /// issue before and also removed the context deletion... Q_ASSERT(first->m_instantiatedFrom == this); first->setInstantiatedFrom(0, InstantiationInformation()); Q_ASSERT(first->m_instantiatedFrom == 0); l.relock(); } } //Overridden to instantiate all not yet instantiated local declarations virtual QVector localDeclarations(const TopDUContext* source) const override { if(m_instantiatedFrom && source && BaseContext::type() != DUContext::Template) { QVector decls = m_instantiatedFrom->localDeclarations(source); // DUContext::DeclarationList temp; InstantiationInformation inf; inf.previousInstantiationInformation = m_instantiatedWith; foreach( Declaration* baseDecl, decls ) { TemplateDeclaration* tempDecl = dynamic_cast(baseDecl); if(tempDecl) { tempDecl->instantiate(inf, source); }else{ qDebug() << "Strange: non-template within template context"; } } } return BaseContext::localDeclarations(source); } private: ~CppDUContext() { if(m_instantiatedFrom) setInstantiatedFrom(0, InstantiationInformation()); deleteAllInstantiations(); } virtual void mergeDeclarationsInternal(QList< QPair >& definitions, const CursorInRevision& position, QHash& hadContexts, const TopDUContext* source, bool searchInParents, int currentDepth) const override { Q_ASSERT(source); // qDebug() << "checking in" << this->scopeIdentifier(true).toString(); if( m_instantiatedFrom ) { //We need to make sure that all declarations from the specialization-base are instantiated, so they are returned. //This requests all declarations, so they all will be instantiated and instances of them added into this context. //DUContext::mergeDeclarationsInternal will then get them. //Calling localDeclarations() instantiates all not yet instantiated member declarations localDeclarations(source); // qDebug() << "final count of local declarations:" << this->localDeclarations().count(); //Instantiate up-propagating child-contexts with the correct same instantiation-information //This for examples makes unnamed enums accessible InstantiationInformation inf; inf.previousInstantiationInformation = m_instantiatedWith; foreach(DUContext* child, m_instantiatedFrom->childContexts()) { // qDebug() << "checking child-context" << child->isPropagateDeclarations(); if(child->isPropagateDeclarations()) static_cast*>(static_cast*>(child)->instantiate(inf, source))->mergeDeclarationsInternal(definitions, position, hadContexts, source, searchInParents, currentDepth); } } BaseContext::mergeDeclarationsInternal(definitions, position, hadContexts, source, searchInParents, currentDepth); } CppDUContext* m_instantiatedFrom; ///Every access to m_instatiations must be serialized through instatiationsMutex, because they may be written without a write-lock QHash* > m_instatiations; IndexedInstantiationInformation m_instantiatedWith; }; ///Returns whether the given declaration depends on a missing template-parameter bool isTemplateDependent(const Declaration* decl); bool isTemplateDependent(const DUContext* context); } #endif diff --git a/languages/plugins/custom-definesandincludes/compilerprovider/settingsmanager.cpp b/languages/plugins/custom-definesandincludes/compilerprovider/settingsmanager.cpp index a02cc3620e..f433cc1696 100644 --- a/languages/plugins/custom-definesandincludes/compilerprovider/settingsmanager.cpp +++ b/languages/plugins/custom-definesandincludes/compilerprovider/settingsmanager.cpp @@ -1,363 +1,347 @@ /* * This file is part of KDevelop * * Copyright 2010 Andreas Pakulat * Copyright 2014 Sergey Kalinichev * * 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) 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 "settingsmanager.h" #include #include +#include +#include #include #include #include #include #include #include #include "compilerprovider.h" using namespace KDevelop; namespace { namespace ConfigConstants { const QString configKey = QLatin1String( "CustomDefinesAndIncludes" ); const QString definesKey = QLatin1String( "Defines" ); const QString includesKey = QLatin1String( "Includes" ); const QString projectPathPrefix = QLatin1String( "ProjectPath" ); const QString projectPathKey = QLatin1String( "Path" ); const QString customBuildSystemGroup = QLatin1String( "CustomBuildSystem" ); const QString definesAndIncludesGroup = QLatin1String( "Defines And Includes" ); const QString compilersGroup = QLatin1String( "Compilers" ); const QString compilerNameKey = QLatin1String( "Name" ); const QString compilerPathKey = QLatin1String( "Path" ); const QString compilerTypeKey = QLatin1String( "Type" ); QString parserArguments(){ return QStringLiteral("parserArguments"); } } // the grouplist is randomly sorted b/c it uses QSet internally // we sort the keys here, as the structure is properly defined for us QStringList sorted(QStringList list) { std::sort(list.begin(), list.end()); return list; } -QString defaultArguments(){ return QStringLiteral("-fspell-checking -Wdocumentation -std=c++11 -Wall"); } +QString defaultArguments(){ return QStringLiteral("-ferror-limit=100 -fspell-checking -Wdocumentation -Wunused-parameter -Wunreachable-code -Wall -std=c++11"); } CompilerPointer createCompilerFromConfig(KConfigGroup& cfg) { auto grp = cfg.group("Compiler"); auto name = grp.readEntry( ConfigConstants::compilerNameKey, QString() ); if (name.isEmpty()) { return SettingsManager::globalInstance()->provider()->checkCompilerExists({}); } for (auto c : SettingsManager::globalInstance()->provider()->compilers()) { if (c->name() == name) { return c; } } - auto path = grp.readEntry( ConfigConstants::compilerPathKey, QString() ); - auto type = grp.readEntry( ConfigConstants::compilerTypeKey, QString() ); - - auto cf = SettingsManager::globalInstance()->provider()->compilerFactories(); - for (auto f : cf) { - if (f->name() == type) { - auto compiler = f->createCompiler(name, path, true); - SettingsManager::globalInstance()->provider()->registerCompiler(compiler); - return compiler; - } - } - + // Otherwise we have no such compiler registered (broken config file), return default one return SettingsManager::globalInstance()->provider()->checkCompilerExists({}); } void writeCompilerToConfig(KConfigGroup& cfg, const CompilerPointer& compiler) { Q_ASSERT(compiler); auto grp = cfg.group("Compiler"); + // Store only compiler name, path and type retrieved from registered compilers grp.writeEntry(ConfigConstants::compilerNameKey, compiler->name()); - grp.writeEntry(ConfigConstants::compilerPathKey, compiler->path()); - grp.writeEntry(ConfigConstants::compilerTypeKey, compiler->factoryName()); } void doWriteSettings( KConfigGroup grp, const QList& paths ) { int pathIndex = 0; for ( const auto& path : paths ) { KConfigGroup pathgrp = grp.group( ConfigConstants::projectPathPrefix + QString::number( pathIndex++ ) ); pathgrp.writeEntry(ConfigConstants::projectPathKey, path.path); pathgrp.writeEntry(ConfigConstants::parserArguments(), path.parserArguments); { int index = 0; KConfigGroup includes(pathgrp.group(ConfigConstants::includesKey)); for( auto it = path.includes.begin() ; it != path.includes.end(); ++it){ includes.writeEntry(QString::number(++index), *it); } } { KConfigGroup defines(pathgrp.group(ConfigConstants::definesKey)); for (auto it = path.defines.begin(); it != path.defines.end(); ++it) { defines.writeEntry(it.key(), it.value()); } } writeCompilerToConfig(pathgrp, path.compiler); } } /// @param remove if true all read entries will be removed from the config file QList doReadSettings( KConfigGroup grp, bool remove = false ) { QList paths; for( const QString &grpName : sorted(grp.groupList()) ) { if ( !grpName.startsWith( ConfigConstants::projectPathPrefix ) ) { continue; } KConfigGroup pathgrp = grp.group( grpName ); ConfigEntry path; path.path = pathgrp.readEntry( ConfigConstants::projectPathKey, "" ); path.parserArguments = pathgrp.readEntry(ConfigConstants::parserArguments(), defaultArguments()); { // defines // Backwards compatibility with old config style if(pathgrp.hasKey(ConfigConstants::definesKey)) { QByteArray tmp = pathgrp.readEntry( ConfigConstants::definesKey, QByteArray() ); QDataStream s( tmp ); s.setVersion( QDataStream::Qt_4_5 ); // backwards compatible reading QHash defines; s >> defines; path.setDefines(defines); } else { KConfigGroup defines(pathgrp.group(ConfigConstants::definesKey)); QMap defMap = defines.entryMap(); path.defines.reserve(defMap.size()); for(auto it = defMap.constBegin(); it != defMap.constEnd(); ++it) { QString key = it.key(); if(key.isEmpty()) { // Toss out the invalid key and value since a valid // value needs a valid key continue; } else { path.defines.insert(key, it.value()); } } } } { // includes // Backwards compatibility with old config style if(pathgrp.hasKey(ConfigConstants::includesKey)){ QByteArray tmp = pathgrp.readEntry( ConfigConstants::includesKey, QByteArray() ); QDataStream s( tmp ); s.setVersion( QDataStream::Qt_4_5 ); s >> path.includes; } else { KConfigGroup includes(pathgrp.group(ConfigConstants::includesKey)); QMap incMap = includes.entryMap(); for(auto it = incMap.begin(); it != incMap.end(); ++it){ QString value = it.value(); if(value.isEmpty()){ continue; } path.includes += value; } } } path.compiler = createCompilerFromConfig(pathgrp); if ( remove ) { pathgrp.deleteGroup(); } paths << path; } return paths; } /** * Reads and converts paths from old (Custom Build System's) format to the current one. * @return all converted paths (if any) */ QList convertedPaths( KConfig* cfg ) { KConfigGroup group = cfg->group( ConfigConstants::customBuildSystemGroup ); if ( !group.isValid() ) return {}; QList paths; foreach( const QString &grpName, sorted(group.groupList()) ) { KConfigGroup subgroup = group.group( grpName ); if ( !subgroup.isValid() ) continue; paths += doReadSettings( subgroup, true ); } return paths; } } void ConfigEntry::setDefines(const QHash& newDefines) { defines.clear(); defines.reserve(newDefines.size()); for (auto it = newDefines.begin(); it != newDefines.end(); ++it) { defines[it.key()] = it.value().toString(); } } -SettingsManager* SettingsManager::s_globalInstance = nullptr; - -SettingsManager::SettingsManager(bool globalInstance) +SettingsManager::SettingsManager() : m_provider(this) -{ - if (globalInstance) { - Q_ASSERT(!s_globalInstance); - s_globalInstance = this; - } -} +{} SettingsManager::~SettingsManager() -{ - if (s_globalInstance == this) { - s_globalInstance = nullptr; - } -} +{} SettingsManager* SettingsManager::globalInstance() { - Q_ASSERT(s_globalInstance); - return s_globalInstance; + Q_ASSERT(QThread::currentThread() == qApp->thread()); + static SettingsManager s_globalInstance; + return &s_globalInstance; } CompilerProvider* SettingsManager::provider() { return &m_provider; } const CompilerProvider* SettingsManager::provider() const { return &m_provider; } void SettingsManager::writePaths( KConfig* cfg, const QList< ConfigEntry >& paths ) { + Q_ASSERT(QThread::currentThread() == qApp->thread()); + KConfigGroup grp = cfg->group( ConfigConstants::configKey ); if ( !grp.isValid() ) return; grp.deleteGroup(); doWriteSettings( grp, paths ); } QList SettingsManager::readPaths( KConfig* cfg ) const { + Q_ASSERT(QThread::currentThread() == qApp->thread()); + auto converted = convertedPaths( cfg ); if ( !converted.isEmpty() ) { const_cast(this)->writePaths( cfg, converted ); return converted; } KConfigGroup grp = cfg->group( ConfigConstants::configKey ); if ( !grp.isValid() ) { return {}; } return doReadSettings( grp ); } bool SettingsManager::needToReparseCurrentProject( KConfig* cfg ) const { auto grp = cfg->group( ConfigConstants::definesAndIncludesGroup ); return grp.readEntry( "reparse", true ); } void SettingsManager::writeUserDefinedCompilers(const QVector< CompilerPointer >& compilers) { QVector< CompilerPointer > editableCompilers; for (const auto& compiler : compilers) { if (!compiler->editable()) { continue; } editableCompilers.append(compiler); } KConfigGroup config = KSharedConfig::openConfig()->group(ConfigConstants::compilersGroup); config.deleteGroup(); config.writeEntry("number", editableCompilers.count()); int i = 0; for (const auto& compiler : editableCompilers) { KConfigGroup grp = config.group(QString::number(i)); ++i; grp.writeEntry(ConfigConstants::compilerNameKey, compiler->name()); grp.writeEntry(ConfigConstants::compilerPathKey, compiler->path()); grp.writeEntry(ConfigConstants::compilerTypeKey, compiler->factoryName()); } config.sync(); } QVector< CompilerPointer > SettingsManager::userDefinedCompilers() const { QVector< CompilerPointer > compilers; KConfigGroup config = KSharedConfig::openConfig()->group(ConfigConstants::compilersGroup); int count = config.readEntry("number", 0); for (int i = 0; i < count; i++) { KConfigGroup grp = config.group(QString::number(i)); auto name = grp.readEntry(ConfigConstants::compilerNameKey, QString()); auto path = grp.readEntry(ConfigConstants::compilerPathKey, QString()); auto type = grp.readEntry(ConfigConstants::compilerTypeKey, QString()); auto cf = m_provider.compilerFactories(); for (auto f : cf) { if (f->name() == type) { auto compiler = f->createCompiler(name, path); compilers.append(compiler); } } } return compilers; } QString SettingsManager::defaultParserArguments() const { return defaultArguments(); } ConfigEntry::ConfigEntry(const QString& path) : path(path) , compiler(SettingsManager::globalInstance()->provider()->checkCompilerExists({})) {} diff --git a/languages/plugins/custom-definesandincludes/compilerprovider/settingsmanager.h b/languages/plugins/custom-definesandincludes/compilerprovider/settingsmanager.h index d5bec3a8a1..a9bfa31cbb 100644 --- a/languages/plugins/custom-definesandincludes/compilerprovider/settingsmanager.h +++ b/languages/plugins/custom-definesandincludes/compilerprovider/settingsmanager.h @@ -1,78 +1,76 @@ /* * This file is part of KDevelop * * Copyright 2014 Sergey Kalinichev * * 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) 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. * */ #ifndef SETTINGSMANAGER_H #define SETTINGSMANAGER_H #include "../idefinesandincludesmanager.h" #include "compilerprovider.h" #include "icompiler.h" class KConfig; namespace KDevelop { class IProject; class ProjectBaseItem; } struct ConfigEntry { QString path; QStringList includes; KDevelop::Defines defines; CompilerPointer compiler; QString parserArguments; ConfigEntry( const QString& path = QString() ); // FIXME: get rid of this but stay backwards compatible void setDefines(const QHash& defines); }; class SettingsManager { public: - SettingsManager(bool globalInstance = false); ~SettingsManager(); QList readPaths(KConfig* cfg) const; void writePaths(KConfig* cfg, const QList& paths); QVector userDefinedCompilers() const; void writeUserDefinedCompilers(const QVector& compilers); bool needToReparseCurrentProject( KConfig* cfg ) const; QString defaultParserArguments() const; CompilerProvider* provider(); const CompilerProvider* provider() const; static SettingsManager* globalInstance(); private: - + SettingsManager(); CompilerProvider m_provider; - static SettingsManager* s_globalInstance; }; #endif // SETTINGSMANAGER_H diff --git a/languages/plugins/custom-definesandincludes/compilerprovider/tests/test_compilerprovider.cpp b/languages/plugins/custom-definesandincludes/compilerprovider/tests/test_compilerprovider.cpp index 81b53e0958..af147f5459 100644 --- a/languages/plugins/custom-definesandincludes/compilerprovider/tests/test_compilerprovider.cpp +++ b/languages/plugins/custom-definesandincludes/compilerprovider/tests/test_compilerprovider.cpp @@ -1,250 +1,253 @@ /* * This file is part of KDevelop * * Copyright 2014 Sergey Kalinichev * * 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 "test_compilerprovider.h" #include #include #include #include #include #include #include #include #include "../compilerprovider.h" #include "../settingsmanager.h" #include "../tests/projectsgenerator.h" using namespace KDevelop; namespace { -void testCompilerEntry(SettingsManager& settings, KConfig* config){ - auto entries = settings.readPaths(config); +void testCompilerEntry(SettingsManager* settings, KConfig* config){ + auto entries = settings->readPaths(config); auto entry = entries.first(); - auto compilers = settings.provider()->compilers(); + auto compilers = settings->provider()->compilers(); Q_ASSERT(!compilers.isEmpty()); bool gccCompilerInstalled = std::any_of(compilers.begin(), compilers.end(), [](const CompilerPointer& compiler){return compiler->name().contains("gcc", Qt::CaseInsensitive);}); if (gccCompilerInstalled) { QCOMPARE(entry.compiler->name(), QStringLiteral("GCC")); } } -void testAddingEntry(SettingsManager& settings, KConfig* config){ - auto entries = settings.readPaths(config); +void testAddingEntry(SettingsManager* settings, KConfig* config){ + auto entries = settings->readPaths(config); auto entry = entries.first(); - auto compilers = settings.provider()->compilers(); + auto compilers = settings->provider()->compilers(); ConfigEntry otherEntry; otherEntry.defines["TEST"] = "lalal"; otherEntry.includes = QStringList() << "/foo"; otherEntry.path = "test"; otherEntry.compiler = compilers.first(); entries << otherEntry; - settings.writePaths(config, entries); + settings->writePaths(config, entries); - auto readWriteEntries = settings.readPaths(config); + auto readWriteEntries = settings->readPaths(config); QCOMPARE(readWriteEntries.size(), 2); QCOMPARE(readWriteEntries.at(0).path, entry.path); QCOMPARE(readWriteEntries.at(0).defines, entry.defines); QCOMPARE(readWriteEntries.at(0).includes, entry.includes); QCOMPARE(readWriteEntries.at(0).compiler->name(), entry.compiler->name()); QCOMPARE(readWriteEntries.at(1).path, otherEntry.path); QCOMPARE(readWriteEntries.at(1).defines, otherEntry.defines); QCOMPARE(readWriteEntries.at(1).includes, otherEntry.includes); QCOMPARE(readWriteEntries.at(1).compiler->name(), otherEntry.compiler->name()); } } void TestCompilerProvider::initTestCase() { AutoTestShell::init(); TestCore::initialize(); } void TestCompilerProvider::cleanupTestCase() { TestCore::shutdown(); } void TestCompilerProvider::testRegisterCompiler() { - SettingsManager settings; - auto provider = settings.provider(); + auto settings = SettingsManager::globalInstance(); + auto provider = settings->provider(); auto cf = provider->compilerFactories(); for (int i = 0 ; i < cf.size(); ++i) { auto compiler = cf[i]->createCompiler(QString::number(i), QString::number(i)); QVERIFY(provider->registerCompiler(compiler)); QVERIFY(!provider->registerCompiler(compiler)); QVERIFY(provider->compilers().contains(compiler)); } QVERIFY(!provider->registerCompiler({})); } void TestCompilerProvider::testCompilerIncludesAndDefines() { - SettingsManager settings; - auto provider = settings.provider(); + auto settings = SettingsManager::globalInstance(); + auto provider = settings->provider(); for (auto c : provider->compilers()) { if (!c->editable() && !c->path().isEmpty()) { QVERIFY(!c->defines({}).isEmpty()); QVERIFY(!c->includes({}).isEmpty()); } } QVERIFY(!provider->defines(nullptr).isEmpty()); QVERIFY(!provider->includes(nullptr).isEmpty()); auto compiler = provider->compilerForItem(nullptr); QVERIFY(compiler); QVERIFY(!compiler->defines(QStringLiteral("--std=c++11")).isEmpty()); QVERIFY(!compiler->includes(QStringLiteral("--std=c++11")).isEmpty()); } void TestCompilerProvider::testStorageBackwardsCompatible() { - SettingsManager settings; + auto settings = SettingsManager::globalInstance(); QTemporaryFile file; QVERIFY(file.open()); QTextStream stream(&file); stream << "[Buildset]\n" << "BuildItems=@Variant(\\x00\\x00\\x00\\t\\x00\\x00\\x00\\x00\\x01\\x00\\x00\\x00\\x0b\\x00\\x00\\x00\\x00\\x01\\x00\\x00\\x00\\x1a\\x00S\\x00i\\x00m\\x00p\\x00l\\x00e\\x00P\\x00r\\x00o\\x00j\\x00e\\x00c\\x00t)\n" << "[CustomBuildSystem]\n" << "CurrentConfiguration=BuildConfig0\n" << "[CustomDefinesAndIncludes][ProjectPath0]\n" << "Defines=\\x00\\x00\\x00\\x02\\x00\\x00\\x00\\x0c\\x00_\\x00D\\x00E\\x00B\\x00U\\x00G\\x00\\x00\\x00\\n\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x10\\x00V\\x00A\\x00R\\x00I\\x00A\\x00B\\x00L\\x00E\\x00\\x00\\x00\\n\\x00\\x00\\x00\\x00\\n\\x00V\\x00A\\x00L\\x00U\\x00E\n" << "Includes=\\x00\\x00\\x00\\x01\\x00\\x00\\x00$\\x00/\\x00u\\x00s\\x00r\\x00/\\x00i\\x00n\\x00c\\x00l\\x00u\\x00d\\x00e\\x00/\\x00m\\x00y\\x00d\\x00i\\x00r\n" << "Path=/\n" << "[CustomDefinesAndIncludes][ProjectPath0][Compiler]\nName=GCC\nPath=gcc\nType=GCC\n"; file.close(); KConfig config(file.fileName()); - auto entries = settings.readPaths(&config); + auto entries = settings->readPaths(&config); QCOMPARE(entries.size(), 1); auto entry = entries.first(); Defines defines; defines["VARIABLE"] = "VALUE"; defines["_DEBUG"] = QString(); QCOMPARE(entry.defines, defines); QStringList includes = QStringList() << "/usr/include/mydir"; QCOMPARE(entry.includes, includes); QCOMPARE(entry.path, QString("/")); QVERIFY(entry.compiler); testCompilerEntry(settings, &config); testAddingEntry(settings, &config); } void TestCompilerProvider::testStorageNewSystem() { - SettingsManager settings; + auto settings = SettingsManager::globalInstance(); QTemporaryFile file; QVERIFY(file.open()); QTextStream stream(&file); stream << "[Buildset]\n" << "BuildItems=@Variant(\\x00\\x00\\x00\\t\\x00\\x00\\x00\\x00\\x01\\x00\\x00\\x00\\x0b\\x00\\x00\\x00\\x00\\x01\\x00\\x00\\x00\\x1a\\x00S\\x00i\\x00m\\x00p\\x00l\\x00e\\x00P\\x00r\\x00o\\x00j\\x00e\\x00c\\x00t)\n\n" << "[CustomBuildSystem]\n" << "CurrentConfiguration=BuildConfig0\n\n" << "[CustomDefinesAndIncludes][ProjectPath0]\n" << "Path=/\n\n" << "[CustomDefinesAndIncludes][ProjectPath0][Defines]\n" << "_DEBUG=\n" << "VARIABLE=VALUE\n" << "[CustomDefinesAndIncludes][ProjectPath0][Includes]\n" << "1=/usr/include/mydir\n" << "2=/usr/local/include/mydir\n" << "[CustomDefinesAndIncludes][ProjectPath0][Compiler]\nName=GCC\nPath=gcc\nType=GCC\n"; file.close(); KConfig config(file.fileName()); - auto entries = settings.readPaths(&config); + auto entries = settings->readPaths(&config); QCOMPARE(entries.size(), 1); auto entry = entries.first(); QCOMPARE(entry.path, QString("/")); Defines defines; defines["VARIABLE"] = "VALUE"; defines["_DEBUG"] = QString(); QCOMPARE(entry.defines, defines); QMap includeMap; includeMap["1"] = "/usr/include/mydir"; includeMap["2"] = "/usr/local/include/mydir"; int i = 0; for(auto it = includeMap.begin(); it != includeMap.end(); it++) { QCOMPARE(entry.includes.at(i++), it.value()); } testCompilerEntry(settings, &config); testAddingEntry(settings, &config); } void TestCompilerProvider::testCompilerIncludesAndDefinesForProject() { auto project = ProjectsGenerator::GenerateMultiPathProject(); Q_ASSERT(project); - SettingsManager settings; - auto provider = settings.provider(); + auto settings = SettingsManager::globalInstance(); + auto provider = settings->provider(); Q_ASSERT(!provider->compilerFactories().isEmpty()); auto compiler = provider->compilerFactories().first()->createCompiler("name", "path"); QVERIFY(provider->registerCompiler(compiler)); QVERIFY(provider->compilers().contains(compiler)); auto projectCompiler = provider->compilerForItem(project->projectItem()); QVERIFY(projectCompiler); QVERIFY(projectCompiler != compiler); ProjectBaseItem* mainfile = nullptr; for (const auto& file: project->fileSet() ) { for (auto i: project->filesForPath(file)) { if( i->text() == "main.cpp" ) { mainfile = i; break; } } } QVERIFY(mainfile); auto mainCompiler = provider->compilerForItem(mainfile); QVERIFY(mainCompiler); QVERIFY(mainCompiler->name() == projectCompiler->name()); ConfigEntry entry; entry.path = "src/main.cpp"; entry.compiler = compiler; - auto entries = settings.readPaths(project->projectConfiguration().data()); + auto entries = settings->readPaths(project->projectConfiguration().data()); + entries.append(entry); - settings.writePaths(project->projectConfiguration().data(), entries); + settings->writePaths(project->projectConfiguration().data(), entries); + + QVERIFY(provider->compilers().contains(compiler)); mainCompiler = provider->compilerForItem(mainfile); QVERIFY(mainCompiler); QVERIFY(mainCompiler->name() == compiler->name()); ICore::self()->projectController()->closeProject(project); } QTEST_MAIN(TestCompilerProvider) diff --git a/languages/plugins/custom-definesandincludes/definesandincludesmanager.cpp b/languages/plugins/custom-definesandincludes/definesandincludesmanager.cpp index b7b22a1b0a..858dac19c2 100644 --- a/languages/plugins/custom-definesandincludes/definesandincludesmanager.cpp +++ b/languages/plugins/custom-definesandincludes/definesandincludesmanager.cpp @@ -1,287 +1,294 @@ /* * This file is part of KDevelop * * Copyright 2014 Sergey Kalinichev * * 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) 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 "definesandincludesmanager.h" #include "kcm_widget/definesandincludesconfigpage.h" #include "compilerprovider/compilerprovider.h" #include "noprojectincludesanddefines/noprojectincludepathsmanager.h" #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; namespace { ///@return: The ConfigEntry, with includes/defines from @p paths for all parent folders of @p item. static ConfigEntry findConfigForItem(QList paths, const KDevelop::ProjectBaseItem* item) { ConfigEntry ret; const Path itemPath = item->path(); const Path rootDirectory = item->project()->path(); Path closestPath; std::sort(paths.begin(), paths.end(), [] (const ConfigEntry& lhs, const ConfigEntry& rhs) { // sort in reverse order to do a bottom-up search return lhs.path > rhs.path; }); for (const ConfigEntry & entry : paths) { Path targetDirectory = rootDirectory; // note: a dot represents the project root if (entry.path != ".") { targetDirectory.addPath(entry.path); } if (targetDirectory == itemPath || targetDirectory.isParentOf(itemPath)) { ret.includes += entry.includes; for (auto it = entry.defines.constBegin(); it != entry.defines.constEnd(); it++) { if (!ret.defines.contains(it.key())) { ret.defines[it.key()] = it.value(); } } if (ret.parserArguments.isEmpty() || targetDirectory.segments().size() > closestPath.segments().size()) { ret.parserArguments = entry.parserArguments; closestPath = targetDirectory; } } } ret.includes.removeDuplicates(); + + if (ret.parserArguments.isEmpty()) { + ret.parserArguments = SettingsManager::globalInstance()->defaultParserArguments(); + } + return ret; } void merge(Defines* target, const Defines& source) { if (target->isEmpty()) { *target = source; return; } for (auto it = source.constBegin(); it != source.constEnd(); ++it) { target->insert(it.key(), it.value()); } } } K_PLUGIN_FACTORY_WITH_JSON(DefinesAndIncludesManagerFactory, "kdevdefinesandincludesmanager.json", registerPlugin(); ) DefinesAndIncludesManager::DefinesAndIncludesManager( QObject* parent, const QVariantList& ) : IPlugin("kdevdefinesandincludesmanager", parent ) - , m_settings(true) + , m_settings(SettingsManager::globalInstance()) , m_noProjectIPM(new NoProjectIncludePathsManager()) { KDEV_USE_EXTENSION_INTERFACE(IDefinesAndIncludesManager); - registerProvider(m_settings.provider()); + registerProvider(m_settings->provider()); } DefinesAndIncludesManager::~DefinesAndIncludesManager() = default; Defines DefinesAndIncludesManager::defines( ProjectBaseItem* item, Type type ) const { Q_ASSERT(QThread::currentThread() == qApp->thread()); if (!item) { - return m_settings.provider()->defines(nullptr); + return m_settings->provider()->defines(nullptr); } Defines defines; for (auto provider : m_providers) { if (provider->type() & type) { merge(&defines, provider->defines(item)); } } if ( type & ProjectSpecific ) { auto buildManager = item->project()->buildSystemManager(); if ( buildManager ) { merge(&defines, buildManager->defines(item)); } } // Manually set defines have the highest priority and overwrite values of all other types of defines. if (type & UserDefined) { auto cfg = item->project()->projectConfiguration().data(); - merge(&defines, findConfigForItem(m_settings.readPaths(cfg), item).defines); + merge(&defines, findConfigForItem(m_settings->readPaths(cfg), item).defines); } return defines; } Path::List DefinesAndIncludesManager::includes( ProjectBaseItem* item, Type type ) const { Q_ASSERT(QThread::currentThread() == qApp->thread()); if (!item) { - return m_settings.provider()->includes(nullptr); + return m_settings->provider()->includes(nullptr); } Path::List includes; if (type & UserDefined) { auto cfg = item->project()->projectConfiguration().data(); - includes += KDevelop::toPathList(findConfigForItem(m_settings.readPaths(cfg), item).includes); + includes += KDevelop::toPathList(findConfigForItem(m_settings->readPaths(cfg), item).includes); } if ( type & ProjectSpecific ) { auto buildManager = item->project()->buildSystemManager(); if ( buildManager ) { includes += buildManager->includeDirectories(item); } } for (auto provider : m_providers) { if (provider->type() & type) { includes += provider->includes(item); } } return includes; } bool DefinesAndIncludesManager::unregisterProvider(IDefinesAndIncludesManager::Provider* provider) { int idx = m_providers.indexOf(provider); if (idx != -1) { m_providers.remove(idx); return true; } return false; } void DefinesAndIncludesManager::registerProvider(IDefinesAndIncludesManager::Provider* provider) { Q_ASSERT(provider); if (m_providers.contains(provider)) { return; } m_providers.push_back(provider); } Defines DefinesAndIncludesManager::defines(const QString&) const { - return m_settings.provider()->defines(nullptr); + return m_settings->provider()->defines(nullptr); } Path::List DefinesAndIncludesManager::includes(const QString& path) const { - return m_settings.provider()->includes(nullptr) + m_noProjectIPM->includes(path); + return m_settings->provider()->includes(nullptr) + m_noProjectIPM->includes(path); } void DefinesAndIncludesManager::openConfigurationDialog(const QString& pathToFile) { if (auto project = KDevelop::ICore::self()->projectController()->findProjectForUrl(QUrl::fromLocalFile(pathToFile))) { KDevelop::ICore::self()->projectController()->configureProject(project); } else { m_noProjectIPM->openConfigurationDialog(pathToFile); } } Path::List DefinesAndIncludesManager::includesInBackground(const QString& path) const { Path::List includes; for (auto provider: m_backgroundProviders) { includes += provider->includesInBackground(path); } return includes; } Defines DefinesAndIncludesManager::definesInBackground(const QString& path) const { QHash defines; for (auto provider: m_backgroundProviders) { auto result = provider->definesInBackground(path); for (auto it = result.constBegin(); it != result.constEnd(); it++) { defines[it.key()] = it.value(); } } return defines; } bool DefinesAndIncludesManager::unregisterBackgroundProvider(IDefinesAndIncludesManager::BackgroundProvider* provider) { int idx = m_backgroundProviders.indexOf(provider); if (idx != -1) { m_backgroundProviders.remove(idx); return true; } return false; } void DefinesAndIncludesManager::registerBackgroundProvider(IDefinesAndIncludesManager::BackgroundProvider* provider) { Q_ASSERT(provider); if (m_backgroundProviders.contains(provider)) { return; } m_backgroundProviders.push_back(provider); } QString DefinesAndIncludesManager::parserArguments(KDevelop::ProjectBaseItem* item) const { if(!item){ - return m_settings.defaultParserArguments(); + return m_settings->defaultParserArguments(); } + Q_ASSERT(QThread::currentThread() == qApp->thread()); + auto cfg = item->project()->projectConfiguration().data(); - return findConfigForItem(m_settings.readPaths(cfg), item).parserArguments; + return findConfigForItem(m_settings->readPaths(cfg), item).parserArguments; } int DefinesAndIncludesManager::perProjectConfigPages() const { return 1; } ConfigPage* DefinesAndIncludesManager::perProjectConfigPage(int number, const ProjectConfigOptions& options, QWidget* parent) { if (number == 0) { return new DefinesAndIncludesConfigPage(this, options, parent); } return nullptr; } #include "definesandincludesmanager.moc" diff --git a/languages/plugins/custom-definesandincludes/definesandincludesmanager.h b/languages/plugins/custom-definesandincludes/definesandincludesmanager.h index 701977622f..7c6c0d9365 100644 --- a/languages/plugins/custom-definesandincludes/definesandincludesmanager.h +++ b/languages/plugins/custom-definesandincludes/definesandincludesmanager.h @@ -1,79 +1,79 @@ /* * This file is part of KDevelop * * Copyright 2014 Sergey Kalinichev * * 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) 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. * */ #ifndef CUSTOMDEFINESANDINCLUDESMANAGER_H #define CUSTOMDEFINESANDINCLUDESMANAGER_H #include #include #include #include #include "idefinesandincludesmanager.h" #include "compilerprovider/settingsmanager.h" class CompilerProvider; class NoProjectIncludePathsManager; /// @brief: Class for retrieving custom defines and includes. class DefinesAndIncludesManager : public KDevelop::IPlugin, public KDevelop::IDefinesAndIncludesManager { Q_OBJECT Q_INTERFACES( KDevelop::IDefinesAndIncludesManager ) public: explicit DefinesAndIncludesManager( QObject* parent, const QVariantList& args = QVariantList() ); virtual ~DefinesAndIncludesManager(); ///@return list of all custom defines for @p item KDevelop::Defines defines( KDevelop::ProjectBaseItem* item, Type type ) const override; ///@return list of all custom includes for @p item KDevelop::Path::List includes( KDevelop::ProjectBaseItem* item, Type type ) const override; virtual KDevelop::Defines defines( const QString& path ) const override; virtual KDevelop::Path::List includes( const QString& path ) const override; virtual void registerProvider( Provider* provider ) override; virtual bool unregisterProvider( Provider* provider ) override; virtual KDevelop::Path::List includesInBackground( const QString& path ) const override; virtual KDevelop::Defines definesInBackground(const QString& path) const override; virtual void registerBackgroundProvider(BackgroundProvider* provider) override; virtual bool unregisterBackgroundProvider(BackgroundProvider* provider) override; virtual QString parserArguments(KDevelop::ProjectBaseItem* item) const override; virtual void openConfigurationDialog( const QString& pathToFile ) override; virtual int perProjectConfigPages() const override; virtual KDevelop::ConfigPage* perProjectConfigPage(int number, const KDevelop::ProjectConfigOptions& options, QWidget* parent) override; private: QVector m_providers; QVector m_backgroundProviders; - SettingsManager m_settings; + SettingsManager* m_settings; QScopedPointer m_noProjectIPM; }; #endif // CUSTOMDEFINESANDINCLUDESMANAGER_H diff --git a/languages/plugins/custom-definesandincludes/idefinesandincludesmanager.h b/languages/plugins/custom-definesandincludes/idefinesandincludesmanager.h index ca8b4b224c..2f22288ac6 100644 --- a/languages/plugins/custom-definesandincludes/idefinesandincludesmanager.h +++ b/languages/plugins/custom-definesandincludes/idefinesandincludesmanager.h @@ -1,192 +1,194 @@ /* * This file is part of KDevelop * * Copyright 2014 Sergey Kalinichev * * 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) 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. */ #ifndef IDEFINESANDINCLUDES_H #define IDEFINESANDINCLUDES_H #include #include #include #include #include #include #include #include namespace KDevelop { class ProjectBaseItem; class IProject; using Defines = QHash; /** An interface that provides language plugins with include directories/files and defines. * Call IDefinesAndIncludesManager::manager() to get the instance of the plugin. **/ class IDefinesAndIncludesManager { public: /// The type of includes/defines enum Type { CompilerSpecific = 1, ///< Those that compiler provides ProjectSpecific = 2, ///< Those that project manager provides UserDefined = 4, ///< Those that user defines All = CompilerSpecific | ProjectSpecific | UserDefined }; /** * Class that actually does all the work of calculating i/d. * * Implement one in e.g. project manager and register it with @see registerProvider * To unregister it use @see unregisterProvider * * @sa BackgroundProvider **/ class Provider { public: virtual ~Provider() = default; virtual Defines defines( ProjectBaseItem* item ) const = 0; virtual Path::List includes( ProjectBaseItem* item ) const = 0; /// @return the type of i/d this provider provides virtual Type type() const = 0; }; /** * Use this as base class for provider, if computing includes/defines can takes a long time (e.g. parsing Makefile by running make). * * This provider will be queried for includes/defines in a background thread. * * @sa Provider **/ class BackgroundProvider { public: virtual ~BackgroundProvider() = default; virtual Path::List includesInBackground( const QString& path ) const = 0; virtual Defines definesInBackground( const QString& path ) const = 0; /// @return the type of i/d this provider provides virtual Type type() const = 0; }; ///@param item project item ///@return list of defines for @p item ///NOTE: call it from the foreground thread only. virtual Defines defines( ProjectBaseItem* item, Type type = All ) const = 0; ///@param item project item ///@return list of include directories/files for @p item ///NOTE: call it from the foreground thread only. virtual Path::List includes( ProjectBaseItem* item, Type type = All ) const = 0; ///@param path path to an out-of-project file. ///@return list of defines for @p path ///NOTE: call it from the foreground thread only. virtual Defines defines( const QString& path ) const = 0; ///@param path path to an out-of-project file. ///@return list of include directories/files for @p path ///NOTE: call it from the foreground thread only. virtual Path::List includes( const QString& path ) const = 0; /** * Computes include directories in background thread. * * This is especially useful for CustomMake projects. * * Call it from background thread if possible. **/ virtual Path::List includesInBackground( const QString& path ) const = 0; /** * Computes defined macros in background thread. * * Call it from background thread if possible. **/ virtual Defines definesInBackground( const QString& path ) const = 0; /** * @param item project item. Use nullptr to get default arguments * @return The parser command-line arguments used to parse the @p item + * + * NOTE: Call it from the main thread only, the default arguments can also be retrieved from a background thread */ virtual QString parserArguments(ProjectBaseItem* item) const = 0; ///@return the instance of the plugin. inline static IDefinesAndIncludesManager* manager(); virtual ~IDefinesAndIncludesManager() = default; /** * Register the @p provider */ virtual void registerProvider(Provider* provider) = 0; /** * Unregister the provider * * @return true on success, false otherwise (e.g. if not registered) */ virtual bool unregisterProvider(Provider* provider) = 0; /** * Use this to register the background provider * * This provider will be queried for includes/defines in a background thread. */ virtual void registerBackgroundProvider(BackgroundProvider* provider) = 0; /** * Unregister the background provider. * * @sa registerBackgroundProvider */ virtual bool unregisterBackgroundProvider(BackgroundProvider* provider) = 0; /// Opens a configuration dialog for @p pathToFile to modify include directories/files and defined macros. virtual void openConfigurationDialog(const QString& pathToFile) = 0; }; inline IDefinesAndIncludesManager* IDefinesAndIncludesManager::manager() { static QPointer manager; if (!manager) { manager = ICore::self()->pluginController()->pluginForExtension( QStringLiteral("org.kdevelop.IDefinesAndIncludesManager") ); } Q_ASSERT(manager); auto extension = manager->extension(); return extension; } } Q_DECLARE_INTERFACE( KDevelop::IDefinesAndIncludesManager, "org.kdevelop.IDefinesAndIncludesManager" ) #endif diff --git a/languages/plugins/custom-definesandincludes/includepathsconverter.cpp b/languages/plugins/custom-definesandincludes/includepathsconverter.cpp index 080a116ef5..ce84c5226d 100644 --- a/languages/plugins/custom-definesandincludes/includepathsconverter.cpp +++ b/languages/plugins/custom-definesandincludes/includepathsconverter.cpp @@ -1,246 +1,245 @@ /* * This file is part of KDevelop * * Copyright 2014 Sergey Kalinichev * * 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) 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 "includepathsconverter.h" #include #include #include #include #include #include #include #include "settingsmanager.h" using namespace KDevelop; namespace { KSharedConfigPtr openConfigFile(const QString& configFile) { return KSharedConfig::openConfig(configFile, KConfig::SimpleConfig); } QString findconfigFile(const QString& projectDir) { QDirIterator dirIterator(projectDir + '/' + ".kdev4"); while (dirIterator.hasNext()) { dirIterator.next(); if (dirIterator.fileName().endsWith(".kdev4")) { return dirIterator.fileInfo().canonicalFilePath(); } } return {}; } QString findProject(const QString& subdirectory) { QDir project(subdirectory); do { if (project.exists(".kdev4")) { return project.path(); } } while(project.cdUp()); return {}; } } IncludePathsConverter::IncludePathsConverter() -: m_settings(new SettingsManager(true)) { } bool IncludePathsConverter::addIncludePaths(const QStringList& includeDirectories, const QString& projectConfigFile, const QString& subdirectory) { auto configFile = openConfigFile(projectConfigFile); if (!configFile) { return false; } - auto configEntries = m_settings->readPaths(configFile.data()); + auto configEntries = SettingsManager::globalInstance()->readPaths(configFile.data()); QString path = subdirectory.isEmpty() ? "." : subdirectory; ConfigEntry config; for (auto& entry: configEntries) { if (path == entry.path) { config = entry; config.includes += includeDirectories; config.includes.removeDuplicates(); entry = config; break; } } if (config.path.isEmpty()) { config.path = path; config.includes = includeDirectories; configEntries.append(config); } - m_settings->writePaths(configFile.data(), configEntries); + SettingsManager::globalInstance()->writePaths(configFile.data(), configEntries); return true; } bool IncludePathsConverter::removeIncludePaths(const QStringList& includeDirectories, const QString& projectConfigFile, const QString& subdirectory) { auto configFile = openConfigFile(projectConfigFile); if (!configFile) { return false; } - auto configEntries = m_settings->readPaths(configFile.data()); + auto configEntries = SettingsManager::globalInstance()->readPaths(configFile.data()); QString path = subdirectory.isEmpty() ? "." : subdirectory; for (auto& entry: configEntries) { if (path == entry.path) { for(const auto& include: includeDirectories) { entry.includes.removeAll(include); } - m_settings->writePaths(configFile.data(), configEntries); + SettingsManager::globalInstance()->writePaths(configFile.data(), configEntries); return true; } } return true; } QStringList IncludePathsConverter::readIncludePaths(const QString& projectConfigFile, const QString& subdirectory) const { auto configFile = openConfigFile(projectConfigFile); if (!configFile) { return {}; } QString path = subdirectory.isEmpty() ? "." : subdirectory; - auto configEntries = m_settings->readPaths(configFile.data()); + auto configEntries = SettingsManager::globalInstance()->readPaths(configFile.data()); for (const auto& entry: configEntries) { if (path == entry.path) { return entry.includes; } } return {}; } int main(int argc, char** argv) { QCoreApplication app(argc, argv); QCoreApplication::setApplicationName("kdev_includepathsconverter"); QCommandLineParser parser; parser.setApplicationDescription("\nAdds, removes or shows include directories of a project. Also it can be used as a tool to convert include directories from .kdev_include_paths file to the new format.\n\n" "Examples:\ncat /project/path/.kdev_include_paths | xargs -d '\\n' kdev_includepathsconverter -a /project/path/\n\n" "kdev_includepathsconverter -r /project/path/subdirectory/ \"/some/include/dir\" \"/another/include/dir\" \n\n" "kdev_includepathsconverter -l /project/path/another/subdirectory/"); parser.addHelpOption(); QCommandLineOption listOption("l", QCoreApplication::translate("main", "Shows include directories used by the project"), QCoreApplication::translate("main", "project")); parser.addOption(listOption); QCommandLineOption addOption("a", QCoreApplication::translate("main", "Adds include directories to the project"), QCoreApplication::translate("main", "project")); parser.addOption(addOption); QCommandLineOption removeOption("r", QCoreApplication::translate("main", "Removes include directories from the project"), QCoreApplication::translate("main", "project")); parser.addOption(removeOption); parser.process(app); QString projectDir; QStringList includeDirectories(parser.positionalArguments()); std::transform(includeDirectories.begin(), includeDirectories.end(), includeDirectories.begin(), [](const QString& path) { return path.trimmed(); } ); includeDirectories.erase(std::remove_if(includeDirectories.begin(), includeDirectories.end(), [](const QString& path) { return path.isEmpty(); } ), includeDirectories.end()); bool show = parser.isSet(listOption); bool add = parser.isSet(addOption); bool remove = parser.isSet(removeOption); if (show) { projectDir = parser.value(listOption); } else if(add) { projectDir = parser.value(addOption); } else if(remove) { projectDir = parser.value(removeOption); } if (projectDir.isEmpty()) { parser.showHelp(-1); } QString subdirectory = projectDir; projectDir = findProject(projectDir); QString configFile = findconfigFile(projectDir); QTextStream out(stdout); if (configFile.isEmpty()) { out << QCoreApplication::translate("main", "No project found for: ") << subdirectory; return -1; } if (add || remove) { if (includeDirectories.isEmpty()) { parser.showHelp(-1); } } { auto subdirCanonical = QFileInfo(subdirectory).canonicalFilePath(); auto projectCanonical = QFileInfo(projectDir).canonicalFilePath(); if (subdirCanonical != projectCanonical) { subdirectory = subdirCanonical.mid(projectCanonical.size()); if (subdirectory.startsWith('/')) { subdirectory.remove(0,1); } } else { subdirectory.clear(); } } IncludePathsConverter converter; if (remove) { if (!converter.removeIncludePaths(includeDirectories, configFile, subdirectory)) { out << QCoreApplication::translate("main", "Can't remove include paths"); } } if (add) { if (!converter.addIncludePaths(includeDirectories, configFile, subdirectory)) { out << QCoreApplication::translate("main", "Can't add include paths"); } } if (show) { for (const auto& include: converter.readIncludePaths(configFile, subdirectory)) { out << include << "\n"; } } } diff --git a/languages/plugins/custom-definesandincludes/includepathsconverter.h b/languages/plugins/custom-definesandincludes/includepathsconverter.h index bf9d855a20..0dcbb67ed8 100644 --- a/languages/plugins/custom-definesandincludes/includepathsconverter.h +++ b/languages/plugins/custom-definesandincludes/includepathsconverter.h @@ -1,54 +1,48 @@ /* * This file is part of KDevelop * * Copyright 2014 Sergey Kalinichev * * 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) 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. * */ #ifndef INCLUDEPATHSCONVERTER_H #define INCLUDEPATHSCONVERTER_H #include -#include - -class SettingsManager; class IncludePathsConverter { public: IncludePathsConverter(); /** * Adds @p includeDirectories into @p projectConfigFile */ bool addIncludePaths(const QStringList& includeDirectories, const QString& projectConfigFile, const QString& subdirectory = QString()); /** * Removes @p includeDirectories from @p projectConfigFile */ bool removeIncludePaths(const QStringList& includeDirectories, const QString& projectConfigFile, const QString& subdirectory = QString()); /** * @return include directories retrieved from @p projectConfigFile */ QStringList readIncludePaths(const QString& projectConfigFile, const QString& subdirectory = QString()) const; - -private: - QScopedPointer m_settings; }; #endif // INCLUDEPATHSCONVERTER_H diff --git a/languages/plugins/custom-definesandincludes/tests/projectsgenerator.cpp b/languages/plugins/custom-definesandincludes/tests/projectsgenerator.cpp index b3179567e8..5a9e2cad9d 100644 --- a/languages/plugins/custom-definesandincludes/tests/projectsgenerator.cpp +++ b/languages/plugins/custom-definesandincludes/tests/projectsgenerator.cpp @@ -1,175 +1,204 @@ /* * Copyright 2014 Sergey Kalinichev * * 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 "projectsgenerator.h" #include #include #include #include #include #include #include #include #include using namespace KDevelop; namespace { /// @param projectFile projectName.kdev4 file IProject* loadProject( const QString& projectFile, const QString& projectName ) { KDevSignalSpy* projectSpy = new KDevSignalSpy( ICore::self()->projectController(), SIGNAL( projectOpened( KDevelop::IProject* ) ) ); ICore::self()->projectController()->openProject( QUrl::fromLocalFile(projectFile) ); if( !projectSpy->wait( 5000 ) ) { qFatal("Expected project to be loaded within 5 seconds, but this didn't happen"); } IProject* project = ICore::self()->projectController()->findProjectByName( projectName ); return project; } void createFile( QFile& file ) { file.remove(); if ( !file.open( QIODevice::ReadWrite ) ) { qFatal("Cannot create the file %s", file.fileName().toUtf8().constData()); } } } IProject* ProjectsGenerator::GenerateSimpleProject() { // directory structure: // ./simpleproject.kdev4 // ./src/main.cpp // ./.kdev4/simpleproject.kdev4 const QString sp = QLatin1String( "simpleproject" ); auto rootFolder = QDir::temp(); QDir(rootFolder.absolutePath() + "/" + sp).removeRecursively(); rootFolder.mkdir( sp ); rootFolder.cd( sp ); rootFolder.mkdir( "src" ); rootFolder.mkdir( ".kdev4" ); { QFile file( rootFolder.filePath( "simpleproject.kdev4" ) ); createFile( file ); QTextStream stream1( &file ); stream1 << "[Project]\nName=SimpleProject\nManager=KDevCustomBuildSystem"; } { QFile file( rootFolder.filePath( "src/main.cpp" ) ); createFile( file ); } { QFile file( rootFolder.filePath( ".kdev4/simpleproject.kdev4" ) ); createFile( file ); QTextStream stream( &file ); stream << "[Buildset]\n" << "BuildItems=@Variant(\\x00\\x00\\x00\\t\\x00\\x00\\x00\\x00\\x01\\x00\\x00\\x00\\x0b\\x00\\x00\\x00\\x00\\x01\\x00\\x00\\x00\\x1a\\x00S\\x00i\\x00m\\x00p\\x00l\\x00e\\x00P\\x00r\\x00o\\x00j\\x00e\\x00c\\x00t)\n" << "[CustomBuildSystem]\n" << "CurrentConfiguration=BuildConfig0\n" << "[CustomDefinesAndIncludes][ProjectPath0]\n" << "Defines=\\x00\\x00\\x00\\x02\\x00\\x00\\x00\\x0c\\x00_\\x00D\\x00E\\x00B\\x00U\\x00G\\x00\\x00\\x00\\n\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x10\\x00V\\x00A\\x00R\\x00I\\x00A\\x00B\\x00L\\x00E\\x00\\x00\\x00\\n\\x00\\x00\\x00\\x00\\n\\x00V\\x00A\\x00L\\x00U\\x00E\n" << "Includes=\\x00\\x00\\x00\\x01\\x00\\x00\\x00$\\x00/\\x00u\\x00s\\x00r\\x00/\\x00i\\x00n\\x00c\\x00l\\x00u\\x00d\\x00e\\x00/\\x00m\\x00y\\x00d\\x00i\\x00r\n" << "Path=/\n" << "[Project]\n" << "VersionControlSupport=\n"; } return loadProject( QDir::tempPath() + "/simpleproject/simpleproject.kdev4", "SimpleProject" ); } IProject* ProjectsGenerator::GenerateMultiPathProject() { // directory structure: // ./multipathproject.kdev4 // ./src/main.cpp // ./anotherFolder/tst.h // ./.kdev4/multipathproject.kdev4 const QString mp = QLatin1String( "multipathproject" ); auto rootFolder = QDir::temp(); QDir(rootFolder.absolutePath() + "/" + mp).removeRecursively(); rootFolder.mkdir( mp ); rootFolder.cd( mp ); rootFolder.mkdir( "src" ); rootFolder.mkdir( ".kdev4" ); rootFolder.mkdir( "anotherFolder" ); { QFile file( rootFolder.filePath( "multipathproject.kdev4" ) ); createFile( file ); QTextStream stream1( &file ); stream1 << "[Project]\nName=MultiPathProject\nManager=KDevCustomBuildSystem"; ; } { QFile file1( rootFolder.filePath( "src/main.cpp" ) ); createFile( file1 ); QFile file2( rootFolder.filePath( "anotherFolder/tst.h" ) ); createFile( file2 ); } { QFile file( rootFolder.filePath( ".kdev4/multipathproject.kdev4" ) ); createFile( file ); QTextStream stream( &file ); stream << "[Buildset]\n" << "BuildItems=@Variant(\\x00\\x00\\x00\\t\\x00\\x00\\x00\\x00\\x01\\x00\\x00\\x00\\x0b\\x00\\x00\\x00\\x00\\x01\\x00\\x00\\x00 \\x00M\\x00u\\x00l\\x00t\\x00i\\x00P\\x00a\\x00t\\x00h\\x00P\\x00r\\x00o\\x00j\\x00e\\x00c\\x00t)\n" << "[CustomBuildSystem]\n" << "CurrentConfiguration=BuildConfig0\n" << "[CustomDefinesAndIncludes][ProjectPath0]\n" << "Defines=\\x00\\x00\\x00\\x02\\x00\\x00\\x00\\n\\x00_\\x00C\\x00O\\x00P\\x00Y\\x00\\x00\\x00\\n\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x0c\\x00S\\x00O\\x00U\\x00R\\x00C\\x00E\\x00\\x00\\x00\\n\\x00\\x00\\x00\\x00\\x0e\\x00C\\x00O\\x00N\\x00T\\x00E\\x00N\\x00T\n" << "Includes=\\x00\\x00\\x00\\x01\\x00\\x00\\x00*\\x00/\\x00u\\x00s\\x00r\\x00/\\x00i\\x00n\\x00c\\x00l\\x00u\\x00d\\x00e\\x00/\\x00o\\x00t\\x00h\\x00e\\x00r\\x00d\\x00i\\x00r\n" << "Path=.\n" << "[CustomDefinesAndIncludes][ProjectPath1]\n" << "Defines=\\x00\\x00\\x00\\x01\\x00\\x00\\x00\\n\\x00B\\x00U\\x00I\\x00L\\x00D\\x00\\x00\\x00\\n\\x00\\x00\\x00\\x00\\n\\x00d\\x00e\\x00b\\x00u\\x00g\n" << "Includes=\\x00\\x00\\x00\\x01\\x00\\x00\\x000\\x00/\\x00u\\x00s\\x00r\\x00/\\x00l\\x00o\\x00c\\x00a\\x00l\\x00/\\x00i\\x00n\\x00c\\x00l\\x00u\\x00d\\x00e\\x00/\\x00m\\x00y\\x00d\\x00i\\x00r\n" << "Path=src\n" << "[CustomDefinesAndIncludes][ProjectPath2]\n" << "Defines=\\x00\\x00\\x00\\x01\\x00\\x00\\x00\\x0c\\x00H\\x00I\\x00D\\x00D\\x00E\\x00N\\x00\\x00\\x00\\n\\x00\\x00\\x00\\x00\\x00\n" << "Path=anotherFolder\n" << "[Project]\n" << "VersionControlSupport=\n"; } return loadProject( QDir::tempPath() + "/multipathproject/multipathproject.kdev4", "MultiPathProject" ); } IProject* ProjectsGenerator::GenerateSimpleProjectWithOutOfProjectFiles() { auto project = GenerateSimpleProject(); Q_ASSERT(project); auto rootFolder = QDir(project->path().path()); const QString includePaths = ".kdev_include_paths"; QFile file(rootFolder.filePath(includePaths)); createFile(file); QTextStream stream( &file ); stream << "." + QDir::separator() + "include1.h" << endl << rootFolder.canonicalPath() + QDir::separator() + "include2.h"; return project; } + +IProject* ProjectsGenerator::GenerateEmptyProject() +{ + // directory structure: + // ./emptyproject.kdev4 + // ./.kdev4/emptyproject.kdev4 + + const QString ep = QLatin1String("emptyproject"); + auto rootFolder = QDir::temp(); + QDir(rootFolder.absolutePath() + "/" + ep).removeRecursively(); + rootFolder.mkdir(ep); + rootFolder.cd(ep); + rootFolder.mkdir(".kdev4"); + + { + QFile file(rootFolder.filePath("emptyproject.kdev4")); + createFile(file); + QTextStream stream(&file); + stream << "[Project]\nName=EmptyProject\nManager=KDevCustomBuildSystem"; + } + + { + QFile file(rootFolder.filePath(".kdev4/emptyproject.kdev4")); + createFile(file); + QTextStream stream(&file); + stream << "[Project]\n" << "VersionControlSupport=\n"; + } + return loadProject(QDir::tempPath() + "/emptyproject/emptyproject.kdev4", "EmptyProject"); +} diff --git a/languages/plugins/custom-definesandincludes/tests/projectsgenerator.h b/languages/plugins/custom-definesandincludes/tests/projectsgenerator.h index d274c054fb..54215f2de9 100644 --- a/languages/plugins/custom-definesandincludes/tests/projectsgenerator.h +++ b/languages/plugins/custom-definesandincludes/tests/projectsgenerator.h @@ -1,41 +1,43 @@ /* * Copyright 2014 Sergey Kalinichev * * 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 PROJECTSGENERATOR_H #define PROJECTSGENERATOR_H namespace KDevelop { class IProject; } /// Simple class for generating projects at runtime for testing purposes. class ProjectsGenerator { public: static KDevelop::IProject* GenerateSimpleProject(); static KDevelop::IProject* GenerateSimpleProjectWithOutOfProjectFiles(); static KDevelop::IProject* GenerateMultiPathProject(); + + static KDevelop::IProject* GenerateEmptyProject(); }; #endif // PROJECTSGENERATOR_H diff --git a/languages/plugins/custom-definesandincludes/tests/test_definesandincludes.cpp b/languages/plugins/custom-definesandincludes/tests/test_definesandincludes.cpp index af7a70a4e9..83d649f09c 100644 --- a/languages/plugins/custom-definesandincludes/tests/test_definesandincludes.cpp +++ b/languages/plugins/custom-definesandincludes/tests/test_definesandincludes.cpp @@ -1,132 +1,155 @@ /************************************************************************ * * * Copyright 2010 Andreas Pakulat * * Copyright 2014 Sergey Kalinichev * * * * 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 or version 3 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, see . * ************************************************************************/ #include "test_definesandincludes.h" #include "projectsgenerator.h" #include #include #include #include #include #include #include #include #include "idefinesandincludesmanager.h" using namespace KDevelop; static IProject* s_currentProject = nullptr; void TestDefinesAndIncludes::cleanupTestCase() { TestCore::shutdown(); } void TestDefinesAndIncludes::initTestCase() { AutoTestShell::init(); TestCore::initialize(); } void TestDefinesAndIncludes::cleanup() { ICore::self()->projectController()->closeProject( s_currentProject ); } void TestDefinesAndIncludes::loadSimpleProject() { s_currentProject = ProjectsGenerator::GenerateSimpleProject(); QVERIFY( s_currentProject ); auto manager = IDefinesAndIncludesManager::manager(); QVERIFY( manager ); const auto actualIncludes = manager->includes( s_currentProject->projectItem(), IDefinesAndIncludesManager::UserDefined ); const auto actualDefines = manager->defines( s_currentProject->projectItem(), IDefinesAndIncludesManager::UserDefined ); qDebug() << actualDefines << actualIncludes; QCOMPARE( actualIncludes, Path::List() << Path( "/usr/include/mydir") ); Defines defines; defines.insert( "_DEBUG", "" ); defines.insert( "VARIABLE", "VALUE" ); QCOMPARE( actualDefines, defines ); + + QVERIFY(!manager->parserArguments(s_currentProject->projectItem()).isEmpty()); + QVERIFY(!manager->parserArguments(nullptr).isEmpty()); } void TestDefinesAndIncludes::loadMultiPathProject() { s_currentProject = ProjectsGenerator::GenerateMultiPathProject(); QVERIFY( s_currentProject ); auto manager = IDefinesAndIncludesManager::manager(); QVERIFY( manager ); Path::List includes = Path::List() << Path("/usr/include/otherdir"); QHash defines; defines.insert("SOURCE", "CONTENT"); defines.insert("_COPY", ""); QCOMPARE( manager->includes( s_currentProject->projectItem(), IDefinesAndIncludesManager::UserDefined ), includes ); QCOMPARE( manager->defines( s_currentProject->projectItem(), IDefinesAndIncludesManager::UserDefined ), defines ); + QVERIFY(!manager->parserArguments(s_currentProject->projectItem()).isEmpty()); ProjectBaseItem* mainfile = 0; for (const auto& file: s_currentProject->fileSet() ) { for (auto i: s_currentProject->filesForPath(file)) { if( i->text() == "main.cpp" ) { mainfile = i; break; } } } QVERIFY(mainfile); includes.prepend(Path("/usr/local/include/mydir")); defines.insert("BUILD", "debug"); qDebug() << includes << "VS" << manager->includes( mainfile, IDefinesAndIncludesManager::UserDefined ); qDebug() << mainfile << mainfile->path(); QCOMPARE(manager->includes( mainfile, IDefinesAndIncludesManager::UserDefined ), includes); QCOMPARE(defines, manager->defines( mainfile, IDefinesAndIncludesManager::UserDefined )); + QVERIFY(!manager->parserArguments(mainfile).isEmpty()); } void TestDefinesAndIncludes::testNoProjectIncludeDirectories() { s_currentProject = ProjectsGenerator::GenerateSimpleProjectWithOutOfProjectFiles(); QVERIFY(s_currentProject); auto manager = KDevelop::IDefinesAndIncludesManager::manager(); QVERIFY(manager); auto projectIncludes = manager->includes(s_currentProject->projectItem(), IDefinesAndIncludesManager::UserDefined); Path includePath1(s_currentProject->path().path() + QDir::separator() + "include1.h"); Path includePath2(s_currentProject->path().path() + QDir::separator() + "include2.h"); QVERIFY(!projectIncludes.contains(includePath1)); QVERIFY(!projectIncludes.contains(includePath2)); auto noProjectIncludes = manager->includes(s_currentProject->path().path() + "/src/main.cpp"); QVERIFY(noProjectIncludes.contains(includePath1)); QVERIFY(noProjectIncludes.contains(includePath2)); + QVERIFY(!manager->parserArguments(s_currentProject->projectItem()).isEmpty()); +} + +void TestDefinesAndIncludes::testEmptyProject() +{ + s_currentProject = ProjectsGenerator::GenerateEmptyProject(); + QVERIFY(s_currentProject); + + auto manager = KDevelop::IDefinesAndIncludesManager::manager(); + QVERIFY(manager); + + auto projectIncludes = manager->includes(s_currentProject->projectItem()); + auto projectDefines = manager->defines(s_currentProject->projectItem()); + auto parserArguments = manager->parserArguments(s_currentProject->projectItem()); + + QVERIFY(!projectIncludes.isEmpty()); + QVERIFY(!projectDefines.isEmpty()); + QVERIFY(!parserArguments.isEmpty()); } QTEST_MAIN(TestDefinesAndIncludes) diff --git a/languages/plugins/custom-definesandincludes/tests/test_definesandincludes.h b/languages/plugins/custom-definesandincludes/tests/test_definesandincludes.h index 1f056e3325..1174e16199 100644 --- a/languages/plugins/custom-definesandincludes/tests/test_definesandincludes.h +++ b/languages/plugins/custom-definesandincludes/tests/test_definesandincludes.h @@ -1,41 +1,42 @@ /************************************************************************ * * * Copyright 2010 Andreas Pakulat * * * * 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 or version 3 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, see . * ************************************************************************/ #ifndef TEST_DEFINESANDINCLUDES_H #define TEST_DEFINESANDINCLUDES_H #include namespace KDevelop { class TestCore; } class TestDefinesAndIncludes : public QObject { Q_OBJECT private slots: void initTestCase(); void cleanupTestCase(); void cleanup(); void loadSimpleProject(); void loadMultiPathProject(); void testNoProjectIncludeDirectories(); + void testEmptyProject(); }; #endif diff --git a/languages/qmljs/duchain/declarationbuilder.cpp b/languages/qmljs/duchain/declarationbuilder.cpp index 06f6e27ca5..af7591d7e7 100644 --- a/languages/qmljs/duchain/declarationbuilder.cpp +++ b/languages/qmljs/duchain/declarationbuilder.cpp @@ -1,1544 +1,1544 @@ /************************************************************************************* * Copyright (C) 2012 by Aleix Pol * * Copyright (C) 2012 by Milian Wolff * * * * 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) 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 "declarationbuilder.h" #include "debug.h" #include #include #include #include #include #include #include #include #include #include #include "expressionvisitor.h" #include "parsesession.h" #include "functiondeclaration.h" #include "functiontype.h" #include "helper.h" #include "cache.h" #include "frameworks/nodejs.h" #include #include #include #include using namespace KDevelop; DeclarationBuilder::DeclarationBuilder(ParseSession* session) : m_prebuilding(false) { m_session = session; } ReferencedTopDUContext DeclarationBuilder::build(const IndexedString& url, QmlJS::AST::Node* node, ReferencedTopDUContext updateContext) { Q_ASSERT(m_session->url() == url); // The declaration builder needs to run twice, so it can resolve uses of e.g. functions // which are called before they are defined (which is easily possible, due to JS's dynamic nature). if (!m_prebuilding) { qCDebug(KDEV_QMLJS_DUCHAIN) << "building, but running pre-builder first"; auto prebuilder = new DeclarationBuilder(m_session); prebuilder->m_prebuilding = true; updateContext = prebuilder->build(url, node, updateContext); qCDebug(KDEV_QMLJS_DUCHAIN) << "pre-builder finished"; delete prebuilder; if (!m_session->allDependenciesSatisfied()) { qCDebug(KDEV_QMLJS_DUCHAIN) << "dependencies were missing, don't perform the second parsing pass"; return updateContext; } } else { qCDebug(KDEV_QMLJS_DUCHAIN) << "prebuilding"; } return DeclarationBuilderBase::build(url, node, updateContext); } void DeclarationBuilder::startVisiting(QmlJS::AST::Node* node) { DUContext* builtinQmlContext = nullptr; if (QmlJS::isQmlFile(currentContext()) && !currentContext()->url().str().contains("__builtin_qml.qml")) { builtinQmlContext = m_session->contextOfFile( QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("kdevqmljssupport/nodejsmodules/__builtin_qml.qml")) ); } { DUChainWriteLocker lock; // Remove all the imported parent contexts: imports may have been edited // and there musn't be any leftover parent context currentContext()->topContext()->clearImportedParentContexts(); // Initialize Node.js QmlJS::NodeJS::instance().initialize(this); // Built-in QML types (color, rect, etc) if (builtinQmlContext) { topContext()->addImportedParentContext(builtinQmlContext); } } DeclarationBuilderBase::startVisiting(node); } /* * Functions */ template void DeclarationBuilder::declareFunction(QmlJS::AST::Node* node, bool newPrototypeContext, const QualifiedIdentifier& name, const RangeInRevision& nameRange, QmlJS::AST::Node* parameters, const RangeInRevision& parametersRange, QmlJS::AST::Node* body, const RangeInRevision& bodyRange) { setComment(node); // Declare the function QmlJS::FunctionType::Ptr func(new QmlJS::FunctionType); Decl* decl; { DUChainWriteLocker lock; decl = openDeclaration(name, nameRange); decl->setKind(Declaration::Type); func->setDeclaration(decl); decl->setType(func); } openType(func); // Parameters, if any (a function must always have an interal function context, // so always open a context here even if there are no parameters) DUContext* parametersContext = openContext( node + 1, // Don't call setContextOnNode on node, only the body context can be associated with node RangeInRevision(parametersRange.start, bodyRange.end), // Ensure that this context contains both the parameters and the body DUContext::Function, name ); if (parameters) { QmlJS::AST::Node::accept(parameters, this); } // The internal context of the function is its parameter context { DUChainWriteLocker lock; decl->setInternalContext(parametersContext); } // Open the prototype context, if any. This has to be done before the body // because this context is needed for "this" to be properly resolved // in it. if (newPrototypeContext) { DUChainWriteLocker lock; QmlJS::FunctionDeclaration* d = reinterpret_cast(decl); d->setPrototypeContext(openContext( node + 2, // Don't call setContextOnNode on node, only the body context can be associated with node RangeInRevision(parametersRange.start, parametersRange.start), DUContext::Function, // This allows QmlJS::getOwnerOfContext to know that the parent of this context is the function declaration QualifiedIdentifier(name) )); if (name.last() != Identifier(QLatin1String("Object"))) { // Every class inherit from Object QmlJS::importObjectContext(currentContext(), topContext()); } closeContext(); } // Body, if any (it is a child context of the parameters) openContext( node, bodyRange, DUContext::Other, name ); if (body) { QmlJS::AST::Node::accept(body, this); } // Close the body context and then the parameters context closeContext(); closeContext(); } template void DeclarationBuilder::declareParameters(Node* node, QStringRef Node::*typeAttribute) { for (Node *plist = node; plist; plist = plist->next) { const QualifiedIdentifier name(plist->name.toString()); const RangeInRevision range = m_session->locationToRange(plist->identifierToken); AbstractType::Ptr type = (typeAttribute ? typeFromName((plist->*typeAttribute).toString()) : // The typeAttribute attribute of plist contains the type name of the argument AbstractType::Ptr(new IntegralType(IntegralType::TypeMixed)) // No type information, use mixed ); { DUChainWriteLocker lock; openDeclaration(name, range); } openType(type); closeAndAssignType(); if (QmlJS::FunctionType::Ptr funType = currentType()) { funType->addArgument(type); } } } bool DeclarationBuilder::visit(QmlJS::AST::FunctionDeclaration* node) { declareFunction( node, true, // A function declaration always has its own prototype context QualifiedIdentifier(node->name.toString()), m_session->locationToRange(node->identifierToken), node->formals, m_session->locationsToRange(node->lparenToken, node->rparenToken), node->body, m_session->locationsToRange(node->lbraceToken, node->rbraceToken) ); return false; } bool DeclarationBuilder::visit(QmlJS::AST::FunctionExpression* node) { declareFunction( node, false, QualifiedIdentifier(), QmlJS::emptyRangeOnLine(node->functionToken), node->formals, m_session->locationsToRange(node->lparenToken, node->rparenToken), node->body, m_session->locationsToRange(node->lbraceToken, node->rbraceToken) ); return false; } bool DeclarationBuilder::visit(QmlJS::AST::FormalParameterList* node) { declareParameters(node, (QStringRef QmlJS::AST::FormalParameterList::*)nullptr); return DeclarationBuilderBase::visit(node); } bool DeclarationBuilder::visit(QmlJS::AST::UiParameterList* node) { declareParameters(node, &QmlJS::AST::UiParameterList::type); return DeclarationBuilderBase::visit(node); } bool DeclarationBuilder::visit(QmlJS::AST::ReturnStatement* node) { if (QmlJS::FunctionType::Ptr func = currentType()) { AbstractType::Ptr returnType; if (node->expression) { returnType = findType(node->expression).type; } else { returnType = new IntegralType(IntegralType::TypeVoid); } DUChainWriteLocker lock; func->setReturnType(QmlJS::mergeTypes(func->returnType(), returnType)); } return false; // findType has already explored node } void DeclarationBuilder::endVisitFunction() { QmlJS::FunctionType::Ptr func = currentType(); if (func && !func->returnType()) { // A function that returns nothing returns void DUChainWriteLocker lock; func->setReturnType(AbstractType::Ptr(new IntegralType(IntegralType::TypeVoid))); } closeAndAssignType(); } void DeclarationBuilder::endVisit(QmlJS::AST::FunctionDeclaration* node) { DeclarationBuilderBase::endVisit(node); endVisitFunction(); } void DeclarationBuilder::endVisit(QmlJS::AST::FunctionExpression* node) { DeclarationBuilderBase::endVisit(node); endVisitFunction(); } /* * Variables */ void DeclarationBuilder::inferArgumentsFromCall(QmlJS::AST::Node* base, QmlJS::AST::ArgumentList* arguments) { ContextBuilder::ExpressionType expr = findType(base); QmlJS::FunctionType::Ptr func_type = QmlJS::FunctionType::Ptr::dynamicCast(expr.type); DUChainWriteLocker lock; if (!func_type) { return; } auto func_declaration = dynamic_cast(func_type->declaration(topContext())); if (!func_declaration) { return; } // Put the argument nodes in a list that has a definite size QVector argumentDecls = func_declaration->internalContext()->localDeclarations(); QVector args; for (auto argument = arguments; argument; argument = argument->next) { args.append(argument); } // Don't update a function when it is called with the wrong number // of arguments if (args.size() != argumentDecls.count()) { return; } // Update the types of the function arguments QmlJS::FunctionType::Ptr new_func_type(new QmlJS::FunctionType); for (int i=0; iabstractType(); // Merge the current type of the argument with its type in the call expression AbstractType::Ptr call_type = findType(argument->expression).type; AbstractType::Ptr new_type = QmlJS::mergeTypes(current_type, call_type); // Update the declaration of the argument and its type in the function type if (func_declaration->topContext() == topContext()) { new_func_type->addArgument(new_type); argumentDecls.at(i)->setAbstractType(new_type); } // Add a warning if it is possible that the argument types don't match if (!m_prebuilding && !areTypesEqual(current_type, call_type)) { m_session->addProblem(argument, i18n( "Possible type mismatch between the argument type (%1) and the value passed as argument (%2)", current_type->toString(), call_type->toString() ), IProblem::Hint); } } // Replace the function's type with the new type having updated arguments if (func_declaration->topContext() == topContext()) { new_func_type->setReturnType(func_type->returnType()); new_func_type->setDeclaration(func_declaration); func_declaration->setAbstractType(new_func_type.cast()); if (expr.declaration) { // expr.declaration is the variable that contains the function, while // func_declaration is the declaration of the function. They can be // different and both need to be updated expr.declaration->setAbstractType(new_func_type.cast()); } } return; } bool DeclarationBuilder::visit(QmlJS::AST::VariableDeclaration* node) { setComment(m_session->commentForLocation(node->firstSourceLocation()).toUtf8()); const QualifiedIdentifier name(node->name.toString()); const RangeInRevision range = m_session->locationToRange(node->identifierToken); const AbstractType::Ptr type = findType(node->expression).type; { DUChainWriteLocker lock; openDeclaration(name, range); } openType(type); return false; // findType has already explored node } void DeclarationBuilder::endVisit(QmlJS::AST::VariableDeclaration* node) { DeclarationBuilderBase::endVisit(node); closeAndAssignType(); } bool DeclarationBuilder::visit(QmlJS::AST::BinaryExpression* node) { if (node->op == QSOperator::Assign) { ExpressionType leftType = findType(node->left); ExpressionType rightType = findType(node->right); DUChainWriteLocker lock; if (leftType.declaration) { DUContext* leftCtx = leftType.declaration->context(); DUContext* leftInternalCtx = QmlJS::getInternalContext(leftType.declaration); // object.prototype.method = function(){} : when assigning a function // to a variable living in a Class context, set the prototype // context of the function to the context of the variable if (rightType.declaration && leftCtx->type() == DUContext::Class) { auto func = rightType.declaration.dynamicCast(); if (!QmlJS::getOwnerOfContext(leftCtx) && !leftCtx->importers().isEmpty()) { // MyClass.prototype.myfunc declares "myfunc" in a small context // that is imported by MyClass. The prototype of myfunc should // be the context of MyClass, not the small context in which // it has been declared leftCtx = leftCtx->importers().at(0); } if (func && !func->prototypeContext()) { func->setPrototypeContext(leftCtx); } } if (leftType.declaration->topContext() != topContext()) { // Do not modify a declaration of another file } else if (leftType.isPrototype && leftInternalCtx) { // Assigning something to a prototype is equivalent to making it // inherit from a class: "Class.prototype = ClassOrObject;" leftInternalCtx->clearImportedParentContexts(); QmlJS::importDeclarationInContext( leftInternalCtx, rightType.declaration ); } else { // Merge the already-known type of the variable with the new one leftType.declaration->setAbstractType(QmlJS::mergeTypes(leftType.type, rightType.type)); } } return false; // findType has already explored node } return DeclarationBuilderBase::visit(node); } bool DeclarationBuilder::visit(QmlJS::AST::CallExpression* node) { inferArgumentsFromCall(node->base, node->arguments); return false; } bool DeclarationBuilder::visit(QmlJS::AST::NewMemberExpression* node) { inferArgumentsFromCall(node->base, node->arguments); return false; } /* * Arrays */ void DeclarationBuilder::declareFieldMember(const KDevelop::DeclarationPointer& declaration, const QString& member, QmlJS::AST::Node* node, const QmlJS::AST::SourceLocation& location) { if (QmlJS::isPrototypeIdentifier(member)) { // Don't declare "prototype", this is a special member return; } if (!m_session->allDependenciesSatisfied()) { // Don't declare anything automatically if dependencies are missing: the // checks hereafter may pass now but fail later, thus causing disappearing // declarations return; } DUChainWriteLocker lock; QualifiedIdentifier identifier(member); // Declaration must have an internal context so that the member can be added // into it. DUContext* ctx = QmlJS::getInternalContext(declaration); if (!ctx || ctx->topContext() != topContext()) { return; } // No need to re-declare a field if it already exists if (QmlJS::getDeclaration(identifier, ctx, false)) { return; } // The internal context of declaration is already closed and does not contain // location. This can be worked around by opening a new context, declaring the // new field in it, and then adding the context as a parent of // declaration->internalContext(). RangeInRevision range = m_session->locationToRange(location); IntegralType::Ptr type = IntegralType::Ptr(new IntegralType(IntegralType::TypeMixed)); DUContext* importedContext = openContext(node, range, DUContext::Class); Declaration* decl = openDeclaration(identifier, range); decl->setInSymbolTable(false); // This declaration is in an anonymous context, and the symbol table acts as if the declaration was in the global context openType(type); closeAndAssignType(); closeContext(); ctx->addImportedParentContext(importedContext); } bool DeclarationBuilder::visit(QmlJS::AST::FieldMemberExpression* node) { setComment(m_session->commentForLocation(node->firstSourceLocation()).toUtf8()); ExpressionType type = findType(node->base); if (type.declaration) { declareFieldMember( type.declaration, node->name.toString(), node, node->identifierToken ); } return false; // findType has already visited node->base } bool DeclarationBuilder::visit(QmlJS::AST::ArrayMemberExpression* node) { setComment(m_session->commentForLocation(node->firstSourceLocation()).toUtf8()); // When the user types array["new_key"], declare "new_key" as a new field of // array. auto stringLiteral = QmlJS::AST::cast(node->expression); if (!stringLiteral) { return DeclarationBuilderBase::visit(node); } ExpressionType type = findType(node->base); if (type.declaration) { declareFieldMember( type.declaration, stringLiteral->value.toString(), node, stringLiteral->literalToken ); } node->expression->accept(this); return false; // findType has already visited node->base, and we have just visited node->expression } bool DeclarationBuilder::visit(QmlJS::AST::ObjectLiteral* node) { setComment(m_session->commentForLocation(node->firstSourceLocation()).toUtf8()); // Object literals can appear in the "values" property of enumerations. Their // keys must be declared in the enumeration, not in an anonymous class if (currentContext()->type() == DUContext::Enum) { return DeclarationBuilderBase::visit(node); } // Open an anonymous class declaration, with its internal context StructureType::Ptr type(new StructureType); { DUChainWriteLocker lock; ClassDeclaration* decl = openDeclaration( QualifiedIdentifier(), QmlJS::emptyRangeOnLine(node->lbraceToken) ); decl->setKind(Declaration::Type); decl->setInternalContext(openContext( node, m_session->locationsToRange(node->lbraceToken, node->rbraceToken), DUContext::Class )); type->setDeclaration(decl); // Every object literal inherits from Object QmlJS::importObjectContext(currentContext(), topContext()); } openType(type); return DeclarationBuilderBase::visit(node); } bool DeclarationBuilder::visit(QmlJS::AST::PropertyNameAndValue* node) { setComment(node); if (!node->name || !node->value) { return DeclarationBuilderBase::visit(node); } RangeInRevision range(m_session->locationToRange(node->name->propertyNameToken)); QualifiedIdentifier name(QmlJS::getNodeValue(node->name)); // The type of the declaration can either be an enumeration value or the type // of its expression ExpressionType type; bool inSymbolTable = false; if (currentContext()->type() == DUContext::Enum) { // This is an enumeration value auto value = QmlJS::AST::cast(node->value); EnumeratorType::Ptr enumerator(new EnumeratorType); enumerator->setDataType(IntegralType::TypeInt); if (value) { enumerator->setValue((int)value->value); } type.type = AbstractType::Ptr::staticCast(enumerator); type.declaration = nullptr; inSymbolTable = true; } else { // Normal value type = findType(node->value); } // If a function is assigned to an object member, set the prototype context // of the function to the object containing the member if (type.declaration) { DUChainWriteLocker lock; auto func = type.declaration.dynamicCast(); if (func && !func->prototypeContext()) { func->setPrototypeContext(currentContext()); } } // Open the declaration { DUChainWriteLocker lock; ClassMemberDeclaration* decl = openDeclaration(name, range); decl->setInSymbolTable(inSymbolTable); } openType(type.type); return false; // findType has already explored node->expression } void DeclarationBuilder::endVisit(QmlJS::AST::PropertyNameAndValue* node) { DeclarationBuilderBase::endVisit(node); closeAndAssignType(); } void DeclarationBuilder::endVisit(QmlJS::AST::ObjectLiteral* node) { DeclarationBuilderBase::endVisit(node); if (currentContext()->type() != DUContext::Enum) { // Enums are special-cased in visit(ObjectLiteral) closeContext(); closeAndAssignType(); } } /* * plugins.qmltypes files */ void DeclarationBuilder::declareComponent(QmlJS::AST::UiObjectInitializer* node, const RangeInRevision &range, const QualifiedIdentifier &name) { QString baseClass = QmlJS::getQMLAttributeValue(node->members, "prototype").value.section('/', -1, -1); // Declare the component itself StructureType::Ptr type(new StructureType); ClassDeclaration* decl; { DUChainWriteLocker lock; decl = openDeclaration(name, range); decl->setKind(Declaration::Type); decl->setClassType(ClassDeclarationData::Interface); decl->clearBaseClasses(); if (!baseClass.isNull()) { addBaseClass(decl, baseClass); } type->setDeclaration(decl); decl->setType(type); // declareExports needs to know the type of decl } openType(type); } void DeclarationBuilder::declareMethod(QmlJS::AST::UiObjectInitializer* node, const RangeInRevision &range, const QualifiedIdentifier &name, bool isSlot, bool isSignal) { QString type_name = QmlJS::getQMLAttributeValue(node->members, "type").value; QmlJS::FunctionType::Ptr type(new QmlJS::FunctionType); if (type_name.isNull()) { type->setReturnType(typeFromName("void")); } else { type->setReturnType(typeFromName(type_name)); } { DUChainWriteLocker lock; ClassFunctionDeclaration* decl = openDeclaration(name, range); decl->setIsSlot(isSlot); decl->setIsSignal(isSignal); type->setDeclaration(decl); } openType(type); } void DeclarationBuilder::declareProperty(QmlJS::AST::UiObjectInitializer* node, const RangeInRevision &range, const QualifiedIdentifier &name) { AbstractType::Ptr type = typeFromName(QmlJS::getQMLAttributeValue(node->members, "type").value); { DUChainWriteLocker lock; ClassMemberDeclaration* decl = openDeclaration(name, range); decl->setAbstractType(type); } openType(type); } void DeclarationBuilder::declareParameter(QmlJS::AST::UiObjectInitializer* node, const RangeInRevision &range, const QualifiedIdentifier &name) { QmlJS::FunctionType::Ptr function = currentType(); AbstractType::Ptr type = typeFromName(QmlJS::getQMLAttributeValue(node->members, "type").value); Q_ASSERT(function); function->addArgument(type); { DUChainWriteLocker lock; openDeclaration(name, range); } openType(type); } void DeclarationBuilder::declareEnum(const RangeInRevision &range, const QualifiedIdentifier &name) { EnumerationType::Ptr type(new EnumerationType); { DUChainWriteLocker lock; ClassMemberDeclaration* decl = openDeclaration(name, range); decl->setKind(Declaration::Type); decl->setType(type); // The type needs to be set here because closeContext is called before closeAndAssignType and needs to know the type of decl type->setDataType(IntegralType::TypeEnumeration); type->setDeclaration(decl); } openType(type); } void DeclarationBuilder::declareComponentSubclass(QmlJS::AST::UiObjectInitializer* node, const KDevelop::RangeInRevision& range, const QString& baseclass, QmlJS::AST::UiQualifiedId* qualifiedId) { QualifiedIdentifier name( QmlJS::getQMLAttributeValue(node->members, "name").value.section('/', -1, -1) ); DUContext::ContextType contextType = DUContext::Class; if (baseclass == QLatin1String("Component")) { // QML component, equivalent to a QML class declareComponent(node, range, name); } else if (baseclass == QLatin1String("Method") || baseclass == QLatin1String("Signal") || baseclass == QLatin1String("Slot")) { // Method (that can also be a signal or a slot) declareMethod(node, range, name, baseclass == QLatin1String("Slot"), baseclass == QLatin1String("Signal")); contextType = DUContext::Function; } else if (baseclass == QLatin1String("Property")) { // A property declareProperty(node, range, name); } else if (baseclass == QLatin1String("Parameter") && currentType()) { // One parameter of a signal/slot/method declareParameter(node, range, name); } else if (baseclass == QLatin1String("Enum")) { // Enumeration. The "values" key contains a dictionary of name -> number entries. declareEnum(range, name); contextType = DUContext::Enum; name = QualifiedIdentifier(); // Enum contexts should have no name so that their members have the correct scope } else { // Define an anonymous subclass of the baseclass. This subclass will // be instantiated when "id:" is encountered name = QualifiedIdentifier(); // Use ExpressionVisitor to find the declaration of the base class DeclarationPointer baseClass = findType(qualifiedId).declaration; StructureType::Ptr type(new StructureType); { DUChainWriteLocker lock; ClassDeclaration* decl = openDeclaration( currentContext()->type() == DUContext::Global ? QualifiedIdentifier(m_session->moduleName()) : name, QmlJS::emptyRangeOnLine(node->lbraceToken) ); decl->clearBaseClasses(); decl->setKind(Declaration::Type); decl->setType(type); // The class needs to know its type early because it contains definitions that depend on that type type->setDeclaration(decl); if (baseClass) { addBaseClass(decl, baseClass->indexedType()); } } openType(type); } // Open a context of the proper type and identifier openContext( node, m_session->locationsToInnerRange(node->lbraceToken, node->rbraceToken), contextType, name ); DUContext* ctx = currentContext(); Declaration* decl = currentDeclaration(); { // Set the inner context of the current declaration, because nested classes // need to know the inner context of their parents DUChainWriteLocker lock; decl->setInternalContext(ctx); if (contextType == DUContext::Enum) { ctx->setPropagateDeclarations(true); } } // If we have have declared a class, import the context of its base classes registerBaseClasses(); } void DeclarationBuilder::declareComponentInstance(QmlJS::AST::ExpressionStatement* expression) { if (!expression) { return; } auto identifier = QmlJS::AST::cast(expression->expression); if (!identifier) { return; } { DUChainWriteLocker lock; injectContext(topContext()); Declaration* decl = openDeclaration( QualifiedIdentifier(identifier->name.toString()), m_session->locationToRange(identifier->identifierToken) ); closeInjectedContext(); // Put the declaration in the global scope decl->setKind(Declaration::Instance); decl->setType(currentAbstractType()); } closeDeclaration(); } DeclarationBuilder::ExportLiteralsAndNames DeclarationBuilder::exportedNames(QmlJS::AST::ExpressionStatement* exports) { ExportLiteralsAndNames res; if (!exports) { return res; } auto exportslist = QmlJS::AST::cast(exports->expression); if (!exportslist) { return res; } // Explore all the exported symbols for this component and keep only those // having a version compatible with the one of this module QSet knownNames; for (auto it = exportslist->elements; it && it->expression; it = it->next) { auto stringliteral = QmlJS::AST::cast(it->expression); if (!stringliteral) { continue; } // String literal like "Namespace/Class version". QStringList nameAndVersion = stringliteral->value.toString().section('/', -1, -1).split(' '); QString name = nameAndVersion.at(0); QString version = (nameAndVersion.count() > 1 ? nameAndVersion.at(1) : QLatin1String("1.0")); if (!knownNames.contains(name)) { knownNames.insert(name); res.append(qMakePair(stringliteral, name)); } } return res; } void DeclarationBuilder::declareExports(const ExportLiteralsAndNames& exports, ClassDeclaration* classdecl) { DUChainWriteLocker lock; // Create the exported versions of the component for (auto exp : exports) { QmlJS::AST::StringLiteral* literal = exp.first; QString name = exp.second; StructureType::Ptr type(new StructureType); injectContext(currentContext()->parentContext()); // Don't declare the export in its C++-ish component, but in the scope above ClassDeclaration* decl = openDeclaration( QualifiedIdentifier(name), m_session->locationToRange(literal->literalToken) ); closeInjectedContext(); // The exported version inherits from the C++ component decl->setKind(Declaration::Type); decl->setClassType(ClassDeclarationData::Class); decl->clearBaseClasses(); type->setDeclaration(decl); addBaseClass(decl, classdecl->indexedType()); // Open a context for the exported class, and register its base class in it decl->setInternalContext(openContext( literal, DUContext::Class, QualifiedIdentifier(name) )); registerBaseClasses(); closeContext(); openType(type); closeAndAssignType(); } } /* * UI */ void DeclarationBuilder::importDirectory(const QString& directory, QmlJS::AST::UiImport* node) { DUChainWriteLocker lock; QString currentFilePath = currentContext()->topContext()->url().str(); QFileInfo dir(directory); QFileInfoList entries; if (dir.isDir()) { // Import all the files in the given directory entries = QDir(directory).entryInfoList( QStringList() << (QLatin1String("*.") + currentFilePath.section(QLatin1Char('.'), -1, -1)) << QLatin1String("*.qmltypes") << QLatin1String("*.so"), QDir::Files ); } else if (dir.isFile()) { // Import the specific file given in the import statement entries.append(dir); } else if (!m_prebuilding) { m_session->addProblem(node, i18n("Module not found, some types or properties may not be recognized")); return; } // Translate the QFileInfos into QStrings (and replace .so files with // qmlplugindump dumps) lock.unlock(); QStringList filePaths = QmlJS::Cache::instance().getFileNames(entries); lock.lock(); if (node && !node->importId.isEmpty()) { // Open a namespace that will contain the declarations QualifiedIdentifier identifier(node->importId.toString()); RangeInRevision range = m_session->locationToRange(node->importIdToken); Declaration* decl = openDeclaration(identifier, range); decl->setKind(Declaration::Namespace); decl->setInternalContext(openContext(node, range, DUContext::Class, identifier)); } for (const QString& filePath : filePaths) { if (filePath == currentFilePath) { continue; } ReferencedTopDUContext context = m_session->contextOfFile(filePath); if (context) { currentContext()->addImportedParentContext(context.data()); } } if (node && !node->importId.isEmpty()) { // Close the namespace containing the declarations closeContext(); closeDeclaration(); } } void DeclarationBuilder::importModule(QmlJS::AST::UiImport* node) { QmlJS::AST::UiQualifiedId *part = node->importUri; QString uri; while (part) { if (!uri.isEmpty()) { uri.append('.'); } uri.append(part->name.toString()); part = part->next; } // Version of the import QString version = m_session->symbolAt(node->versionToken); // Import the directory containing the module QString modulePath = QmlJS::Cache::instance().modulePath(m_session->url(), uri, version); importDirectory(modulePath, node); } bool DeclarationBuilder::visit(QmlJS::AST::UiImport* node) { if (node->importUri) { importModule(node); } else if (!node->fileName.isNull() && node->fileName != QLatin1String(".")) { QUrl currentFileUrl = currentContext()->topContext()->url().toUrl(); QUrl importUrl = QUrl(node->fileName.toString()); importDirectory(currentFileUrl.resolved(importUrl).toLocalFile(), node); } return DeclarationBuilderBase::visit(node); } bool DeclarationBuilder::visit(QmlJS::AST::UiObjectDefinition* node) { setComment(node); // Do not crash if the user has typed an empty object definition if (!node->initializer || !node->initializer->members) { m_skipEndVisit.push(true); return DeclarationBuilderBase::visit(node); } RangeInRevision range(m_session->locationToRange(node->qualifiedTypeNameId->identifierToken)); QString baseclass = node->qualifiedTypeNameId->name.toString(); // "Component" needs special care: a component that appears only in a future // version of this module, or that already appeared in a former version, must // be skipped because it is useless ExportLiteralsAndNames exports; if (baseclass == QLatin1String("Component")) { QmlJS::AST::Statement* statement = QmlJS::getQMLAttribute(node->initializer->members, QLatin1String("exports")); exports = exportedNames(QmlJS::AST::cast(statement)); if (statement && exports.count() == 0) { // This component has an "exports:" member but no export matched // the version of this module. Skip the component m_skipEndVisit.push(true); return false; } } else if (baseclass == QLatin1String("Module")) { // "Module" is disabled. This allows the declarations of a module // dump to appear in the same namespace as the .qml files in the same // directory. m_skipEndVisit.push(true); return true; } // Declare the component subclass declareComponentSubclass(node->initializer, range, baseclass, node->qualifiedTypeNameId); // If we had a component with exported names, declare these exports if (baseclass == QLatin1String("Component")) { ClassDeclaration* classDecl = currentDeclaration(); if (classDecl) { declareExports(exports, classDecl); } } m_skipEndVisit.push(false); return DeclarationBuilderBase::visit(node); } void DeclarationBuilder::endVisit(QmlJS::AST::UiObjectDefinition* node) { DeclarationBuilderBase::endVisit(node); // Do not crash if the user has typed an empty object definition if (!m_skipEndVisit.pop()) { closeContext(); closeAndAssignType(); } } bool DeclarationBuilder::visit(QmlJS::AST::UiScriptBinding* node) { setComment(node); if (!node->qualifiedId) { return DeclarationBuilderBase::visit(node); } // Special-case some binding names QString bindingName = node->qualifiedId->name.toString(); if (bindingName == QLatin1String("id")) { // Instantiate a QML component: its type is the current type (the anonymous // QML class that surrounds the declaration) declareComponentInstance(QmlJS::AST::cast(node->statement)); } // Use ExpressionVisitor to find the signal/property bound DeclarationPointer bindingDecl = findType(node->qualifiedId).declaration; DUChainPointer signal; // If a Javascript block is used as expression or if the script binding is a // slot, open a subcontext so that variables declared in the binding are kept // local, and the signal parameters can be visible to the slot if (( bindingDecl && (signal = bindingDecl.dynamicCast()) && signal->isSignal() ) || node->statement->kind == QmlJS::AST::Node::Kind_Block) { openContext( node->statement, m_session->locationsToInnerRange( node->statement->firstSourceLocation(), node->statement->lastSourceLocation() ), DUContext::Other ); // If this script binding is a slot, import the parameters of its signal if (signal && signal->isSignal() && signal->internalContext()) { DUChainWriteLocker lock; currentContext()->addIndirectImport(DUContext::Import( signal->internalContext(), nullptr )); } } else { // Check that the type of the value matches the type of the property AbstractType::Ptr expressionType = findType(node->statement).type; DUChainReadLocker lock; if (!m_prebuilding && bindingDecl && !areTypesEqual(bindingDecl->abstractType(), expressionType)) { m_session->addProblem(node->qualifiedId, i18n( "Mismatch between the value type (%1) and the property type (%2)", expressionType->toString(), bindingDecl->abstractType()->toString() ), IProblem::Error); } } return DeclarationBuilderBase::visit(node); } void DeclarationBuilder::endVisit(QmlJS::AST::UiScriptBinding* node) { QmlJS::AST::Visitor::endVisit(node); // If visit(UiScriptBinding) has opened a context, close it if (currentContext()->type() == DUContext::Other) { closeContext(); } } bool DeclarationBuilder::visit(QmlJS::AST::UiObjectBinding* node) { setComment(node); if (!node->qualifiedId || !node->qualifiedTypeNameId || !node->initializer) { return DeclarationBuilderBase::visit(node); } // Declare the component subclass. "Behavior on ... {}" is treated exactly // like "Behavior {}". RangeInRevision range = m_session->locationToRange(node->qualifiedTypeNameId->identifierToken); QString baseclass = node->qualifiedTypeNameId->name.toString(); declareComponentSubclass(node->initializer, range, baseclass, node->qualifiedTypeNameId); return DeclarationBuilderBase::visit(node); } void DeclarationBuilder::endVisit(QmlJS::AST::UiObjectBinding* node) { DeclarationBuilderBase::endVisit(node); if (node->qualifiedId && node->qualifiedTypeNameId && node->initializer) { closeContext(); closeAndAssignType(); } } bool DeclarationBuilder::visit(QmlJS::AST::UiPublicMember* node) { setComment(node); RangeInRevision range = m_session->locationToRange(node->identifierToken); QualifiedIdentifier id(node->name.toString()); QString typeName = node->memberType.toString(); bool res = DeclarationBuilderBase::visit(node); // Build the type of the public member if (node->type == QmlJS::AST::UiPublicMember::Signal) { // Open a function declaration corresponding to this signal declareFunction( node, false, QualifiedIdentifier(node->name.toString()), m_session->locationToRange(node->identifierToken), node->parameters, m_session->locationToRange(node->identifierToken), // The AST does not provide the location of the parens nullptr, m_session->locationToRange(node->identifierToken) // A body range must be provided ); // This declaration is a signal and its return type is void { DUChainWriteLocker lock; currentDeclaration()->setIsSignal(true); currentType()->setReturnType(typeFromName("void")); } } else { AbstractType::Ptr type; if (typeName == "alias") { // Property aliases take the type of their aliased property type = findType(node->statement).type; res = false; // findType has already explored node->statement } else { type = typeFromName(typeName); if (node->typeModifier == QLatin1String("list")) { // QML list, noted "list" in the source file ArrayType::Ptr array(new ArrayType); array->setElementType(type); type = array.cast(); } } { DUChainWriteLocker lock; Declaration* decl = openDeclaration(id, range); decl->setInSymbolTable(false); } openType(type); } return res; } void DeclarationBuilder::endVisit(QmlJS::AST::UiPublicMember* node) { DeclarationBuilderBase::endVisit(node); closeAndAssignType(); } /* * Utils */ void DeclarationBuilder::setComment(QmlJS::AST::Node* node) { setComment(m_session->commentForLocation(node->firstSourceLocation()).toUtf8()); } void DeclarationBuilder::closeAndAssignType() { closeType(); Declaration* dec = currentDeclaration(); Q_ASSERT(dec); Q_ASSERT(lastType()); { DUChainWriteLocker lock; dec->setType(lastType()); } closeDeclaration(); } AbstractType::Ptr DeclarationBuilder::typeFromName(const QString& name) { auto type = IntegralType::TypeNone; QString realName = name; // Built-in types if (name == QLatin1String("string")) { type = IntegralType::TypeString; } else if (name == QLatin1String("bool")) { type = IntegralType::TypeBoolean; } else if (name == QLatin1String("int")) { type = IntegralType::TypeInt; } else if (name == QLatin1String("float")) { type = IntegralType::TypeFloat; } else if (name == QLatin1String("double") || name == QLatin1String("real")) { type = IntegralType::TypeDouble; } else if (name == QLatin1String("void")) { type = IntegralType::TypeVoid; } else if (name == QLatin1String("var") || name == QLatin1String("variant")) { type = IntegralType::TypeMixed; } else if (m_session->language() == QmlJS::Dialect::Qml) { // In QML files, some Qt type names need to be renamed to the QML equivalent if (name == QLatin1String("QFont")) { realName = QStringLiteral("Font"); } else if (name == QLatin1String("QColor")) { realName = QStringLiteral("color"); } else if (name == QLatin1String("QDateTime")) { realName = QStringLiteral("date"); } else if (name == QLatin1String("QDate")) { realName = QStringLiteral("date"); } else if (name == QLatin1String("QTime")) { realName = QStringLiteral("time"); } else if (name == QLatin1String("QRect") || name == QLatin1String("QRectF")) { realName = QStringLiteral("rect"); } else if (name == QLatin1String("QPoint") || name == QLatin1String("QPointF")) { realName = QStringLiteral("point"); } else if (name == QLatin1String("QSize") || name == QLatin1String("QSizeF")) { realName = QStringLiteral("size"); } else if (name == QLatin1String("QUrl")) { realName = QStringLiteral("url"); } else if (name == QLatin1String("QVector3D")) { realName = QStringLiteral("vector3d"); } else if (name.endsWith(QLatin1String("ScriptString"))) { // Q{Declarative,Qml}ScriptString represents a JS snippet auto func = new QmlJS::FunctionType; func->setReturnType(AbstractType::Ptr(new IntegralType(IntegralType::TypeVoid))); return AbstractType::Ptr(func); } } if (type == IntegralType::TypeNone) { // Not a built-in type, but a class return typeFromClassName(realName); } else { return AbstractType::Ptr(new IntegralType(type)); } } AbstractType::Ptr DeclarationBuilder::typeFromClassName(const QString& name) { DeclarationPointer decl = QmlJS::getDeclaration(QualifiedIdentifier(name), currentContext()); if (!decl) { if (name == QLatin1String("QRegExp")) { decl = QmlJS::NodeJS::instance().moduleMember(QStringLiteral("__builtin_ecmascript"), QStringLiteral("RegExp"), currentContext()->url()); } } if (decl) { return decl->abstractType(); } else { DelayedType::Ptr type(new DelayedType); type->setKind(DelayedType::Unresolved); type->setIdentifier(IndexedTypeIdentifier(name)); return type; } } void DeclarationBuilder::addBaseClass(ClassDeclaration* classDecl, const QString& name) { addBaseClass(classDecl, typeFromClassName(name)->indexed()); } void DeclarationBuilder::addBaseClass(ClassDeclaration* classDecl, const IndexedType& type) { BaseClassInstance baseClass; baseClass.access = Declaration::Public; baseClass.virtualInheritance = false; baseClass.baseClass = type; classDecl->addBaseClass(baseClass); } void DeclarationBuilder::registerBaseClasses() { ClassDeclaration* classdecl = currentDeclaration(); DUContext *ctx = currentContext(); if (classdecl) { DUChainWriteLocker lock; for (uint i=0; ibaseClassesSize(); ++i) { const BaseClassInstance &baseClass = classdecl->baseClasses()[i]; StructureType::Ptr baseType = StructureType::Ptr::dynamicCast(baseClass.baseClass.abstractType()); TopDUContext* topctx = topContext(); if (baseType && baseType->declaration(topctx)) { QmlJS::importDeclarationInContext(ctx, DeclarationPointer(baseType->declaration(topctx))); } } } } static bool enumContainsEnumerator(const AbstractType::Ptr& a, const AbstractType::Ptr& b) { Q_ASSERT(a->whichType() == AbstractType::TypeEnumeration); auto aEnum = EnumerationType::Ptr::staticCast(a); Q_ASSERT(b->whichType() == AbstractType::TypeEnumerator); auto bEnumerator = EnumeratorType::Ptr::staticCast(a); return bEnumerator->qualifiedIdentifier().beginsWith(aEnum->qualifiedIdentifier()); } static bool isNumeric(const IntegralType::Ptr& type) { return type->dataType() == IntegralType::TypeInt || type->dataType() == IntegralType::TypeIntegral || type->dataType() == IntegralType::TypeFloat || type->dataType() == IntegralType::TypeDouble; } bool DeclarationBuilder::areTypesEqual(const AbstractType::Ptr& a, const AbstractType::Ptr& b) { if (!a || !b) { return true; } if (a->whichType() == AbstractType::TypeUnsure || b->whichType() == AbstractType::TypeUnsure) { // Don't try to guess something if one of the types is unsure return true; } const auto bIntegral = IntegralType::Ptr::dynamicCast(b); if (bIntegral && bIntegral->dataType() == IntegralType::TypeString) { // In QML/JS, a string can be converted to nearly everything else return true; } const auto aIntegral = IntegralType::Ptr::dynamicCast(a); if (aIntegral && bIntegral) { if ((aIntegral->dataType() == IntegralType::TypeMixed) || (bIntegral->dataType() == IntegralType::TypeMixed)) { // Mixed types, don't try to be clever return true; } if (isNumeric(aIntegral) && isNumeric(bIntegral)) { // Casts between integral types is possible return true; } } if (a->whichType() == AbstractType::TypeEnumeration && b->whichType() == AbstractType::TypeEnumerator) { return enumContainsEnumerator(a, b); } else if (a->whichType() == AbstractType::TypeEnumerator && b->whichType() == AbstractType::TypeEnumeration) { return enumContainsEnumerator(b, a); } { auto aId = dynamic_cast(a.constData()); auto bId = dynamic_cast(b.constData()); if (aId && bId && aId->qualifiedIdentifier() == bId->qualifiedIdentifier()) return true; } { auto aStruct = StructureType::Ptr::dynamicCast(a); - auto bStruct = StructureType::Ptr::dynamicCast(a); + auto bStruct = StructureType::Ptr::dynamicCast(b); if (aStruct && bStruct) { auto top = currentContext()->topContext(); auto aDecl = dynamic_cast(aStruct->declaration(top)); auto bDecl = dynamic_cast(bStruct->declaration(top)); if (aDecl && bDecl) { if (aDecl->isPublicBaseClass(bDecl, top) || bDecl->isPublicBaseClass(aDecl, top)) { return true; } } } } return a->equals(b.constData()); } diff --git a/projectmanagers/cmake/cmakemanager.cpp b/projectmanagers/cmake/cmakemanager.cpp index 5e8a31bc15..2bb2832d13 100644 --- a/projectmanagers/cmake/cmakemanager.cpp +++ b/projectmanagers/cmake/cmakemanager.cpp @@ -1,865 +1,874 @@ /* KDevelop CMake Support * * Copyright 2006 Matt Rogers * Copyright 2007-2013 Aleix Pol * * 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) 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 "cmakemanager.h" #include "cmakeedit.h" #include "cmakeutils.h" #include "cmakeprojectdata.h" #include "duchain/cmakeparsejob.h" #include "cmakeimportjsonjob.h" #include "debug.h" #include "settings/cmakepreferences.h" #include #include "cmakecodecompletionmodel.h" #include "cmakenavigationwidget.h" #include "icmakedocumentation.h" #include "cmakemodelitems.h" #include "testing/ctestutils.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include Q_DECLARE_METATYPE(KDevelop::IProject*); using namespace KDevelop; K_PLUGIN_FACTORY_WITH_JSON(CMakeSupportFactory, "kdevcmakemanager.json", registerPlugin(); ) const QString DIALOG_CAPTION = i18n("KDevelop - CMake Support"); CMakeManager::CMakeManager( QObject* parent, const QVariantList& ) : KDevelop::AbstractFileManagerPlugin( "kdevcmakemanager", parent ) , m_filter( new ProjectFilterManager( this ) ) { KDEV_USE_EXTENSION_INTERFACE( KDevelop::IBuildSystemManager ) KDEV_USE_EXTENSION_INTERFACE( KDevelop::IProjectFileManager ) KDEV_USE_EXTENSION_INTERFACE( KDevelop::ILanguageSupport ) KDEV_USE_EXTENSION_INTERFACE( ICMakeManager) if (hasError()) { return; } m_highlight = new KDevelop::CodeHighlighting(this); new CodeCompletion(this, new CMakeCodeCompletionModel(this), name()); connect(ICore::self()->projectController(), &IProjectController::projectClosing, this, &CMakeManager::projectClosing); connect(this, &KDevelop::AbstractFileManagerPlugin::folderAdded, this, &CMakeManager::folderAdded); // m_fileSystemChangeTimer = new QTimer(this); // m_fileSystemChangeTimer->setSingleShot(true); // m_fileSystemChangeTimer->setInterval(100); // connect(m_fileSystemChangeTimer,SIGNAL(timeout()),SLOT(filesystemBuffererTimeout())); } bool CMakeManager::hasError() const { return CMake::findExecutable().isEmpty(); } QString CMakeManager::errorDescription() const { return hasError() ? i18n("cmake is not installed") : QString(); } CMakeManager::~CMakeManager() {} bool CMakeManager::hasIncludesOrDefines(ProjectBaseItem* item) const { return m_projects[item->project()].jsonData.files.contains(item->path()); } Path CMakeManager::buildDirectory(KDevelop::ProjectBaseItem *item) const { // CMakeFolderItem *fi=dynamic_cast(item); // Path ret; // ProjectBaseItem* parent = fi ? fi->formerParent() : item->parent(); // if (parent) // ret=buildDirectory(parent); // else // ret=Path(CMake::currentBuildDir(item->project())); // // if(fi) // ret.addPath(fi->buildDir()); // return ret; return Path(CMake::currentBuildDir(item->project())); } KDevelop::ProjectFolderItem* CMakeManager::import( KDevelop::IProject *project ) { CMake::checkForNeedingConfigure(project); return AbstractFileManagerPlugin::import(project); } KJob* CMakeManager::createImportJob(ProjectFolderItem* item) { auto project = item->project(); QList jobs; // create the JSON file if it doesn't exist auto commandsFile = CMake::commandsFile(project); if (!QFileInfo(commandsFile.toLocalFile()).exists()) { qCDebug(CMAKE) << "couldn't find commands file:" << commandsFile << "- now trying to reconfigure"; jobs << builder()->configure(project); } // parse the JSON file CMakeImportJob* job = new CMakeImportJob(project, this); connect(job, &CMakeImportJob::result, this, &CMakeManager::importFinished); jobs << job; // generate the file system listing jobs << KDevelop::AbstractFileManagerPlugin::createImportJob(item); Q_ASSERT(!jobs.contains(nullptr)); ExecuteCompositeJob* composite = new ExecuteCompositeJob(this, jobs); // even if the cmake call failed, we want to load the project so that the project can be worked on composite->setAbortOnError(false); return composite; } // QList CMakeManager::parse(ProjectFolderItem*) // { return QList< ProjectFolderItem* >(); } // // QList CMakeManager::targets() const { QList ret; foreach(IProject* p, m_projects.keys()) { ret+=p->projectItem()->targetList(); } return ret; } CMakeFile CMakeManager::fileInformation(KDevelop::ProjectBaseItem* item) const { const CMakeJsonData & data = m_projects[item->project()].jsonData; QHash::const_iterator it = data.files.constFind(item->path()); + + if (it == data.files.constEnd()) { + // if the item path contains a symlink, then we will not find it in the lookup table + // as that only only stores canonicalized paths. Thus, we fallback to + // to the canonicalized path and see if that brings up any matches + const auto canonicalized = Path(QFileInfo(item->path().toLocalFile()).canonicalFilePath()); + it = data.files.constFind(canonicalized); + } + if (it != data.files.constEnd()) { return *it; } else { // otherwise look for siblings and use the include paths of any we find const Path folder = item->folder() ? item->path() : item->path().parent(); for( it = data.files.constBegin(); it != data.files.constEnd(); ++it) { if (folder.isDirectParentOf(it.key())) { return *it; } } } // last-resort fallback: bubble up the parent chain, and keep looking for include paths if (auto parent = item->parent()) { return fileInformation(parent); } return {}; } Path::List CMakeManager::includeDirectories(KDevelop::ProjectBaseItem *item) const { return fileInformation(item).includes; } QHash CMakeManager::defines(KDevelop::ProjectBaseItem *item ) const { return fileInformation(item).defines; } KDevelop::IProjectBuilder * CMakeManager::builder() const { IPlugin* i = core()->pluginController()->pluginForExtension( "org.kdevelop.IProjectBuilder", "KDevCMakeBuilder"); Q_ASSERT(i); KDevelop::IProjectBuilder* _builder = i->extension(); Q_ASSERT(_builder ); return _builder ; } bool CMakeManager::reload(KDevelop::ProjectFolderItem* folder) { qCDebug(CMAKE) << "reloading" << folder->path(); IProject* project = folder->project(); if (!project->isReady()) return false; KJob *job = createImportJob(folder); project->setReloadJob(job); ICore::self()->runController()->registerJob( job ); return true; } static void populateTargets(ProjectFolderItem* folder, const QHash& targets) { QStringList dirTargets = targets[folder->path()]; foreach (ProjectTargetItem* item, folder->targetList()) { if(!dirTargets.contains(item->text())) { delete item; } else { dirTargets.removeAll(item->text()); } } foreach (const QString& name, dirTargets) { if (!name.endsWith("_automoc")) new CMakeTargetItem(folder, name); } foreach (ProjectFolderItem* children, folder->folderList()) { populateTargets(children, targets); } } void CMakeManager::importFinished(KJob* j) { CMakeImportJob* job = qobject_cast(j); Q_ASSERT(job); auto project = job->project(); if (job->error() != 0) { qCDebug(CMAKE) << "Import failed for project" << project->name() << job->errorText(); m_projects.remove(project); } qCDebug(CMAKE) << "Successfully imported project" << project->name(); CMakeProjectData data; data.watcher->addPath(CMake::commandsFile(project).toLocalFile()); data.watcher->addPath(CMake::targetDirectoriesFile(project).toLocalFile()); data.jsonData = job->jsonData(); data.targets = job->targets(); connect(data.watcher.data(), &QFileSystemWatcher::fileChanged, this, &CMakeManager::dirtyFile); connect(data.watcher.data(), &QFileSystemWatcher::directoryChanged, this, &CMakeManager::dirtyFile); m_projects[job->project()] = data; populateTargets(job->project()->projectItem(), job->targets()); CTestUtils::createTestSuites(job->testSuites(), project); } // void CMakeManager::deletedWatchedDirectory(IProject* p, const QUrl &dir) // { // if(p->folder().equals(dir, QUrl::CompareWithoutTrailingSlash)) { // ICore::self()->projectController()->closeProject(p); // } else { // if(dir.fileName()=="CMakeLists.txt") { // QList folders = p->foldersForUrl(dir.upUrl()); // foreach(ProjectFolderItem* folder, folders) // reload(folder); // } else { // qDeleteAll(p->itemsForUrl(dir)); // } // } // } // void CMakeManager::directoryChanged(const QString& dir) // { // m_fileSystemChangedBuffer << dir; // m_fileSystemChangeTimer->start(); // } // void CMakeManager::filesystemBuffererTimeout() // { // Q_FOREACH(const QString& file, m_fileSystemChangedBuffer) { // realDirectoryChanged(file); // } // m_fileSystemChangedBuffer.clear(); // } // void CMakeManager::realDirectoryChanged(const QString& dir) // { // QUrl path(dir); // IProject* p=ICore::self()->projectController()->findProjectForUrl(dir); // if(!p || !p->isReady()) { // if(p) { // m_fileSystemChangedBuffer << dir; // m_fileSystemChangeTimer->start(); // } // return; // } // // if(!QFile::exists(dir)) { // path.adjustPath(QUrl::AddTrailingSlash); // deletedWatchedDirectory(p, path); // } else // dirtyFile(dir); // } QList< KDevelop::ProjectTargetItem * > CMakeManager::targets(KDevelop::ProjectFolderItem * folder) const { return folder->targetList(); } QString CMakeManager::name() const { return languageName().str(); } IndexedString CMakeManager::languageName() { static IndexedString name("CMake"); return name; } KDevelop::ParseJob * CMakeManager::createParseJob(const IndexedString &url) { return new CMakeParseJob(url, this); } KDevelop::ICodeHighlighting* CMakeManager::codeHighlighting() const { return m_highlight; } // ContextMenuExtension CMakeManager::contextMenuExtension( KDevelop::Context* context ) // { // if( context->type() != KDevelop::Context::ProjectItemContext ) // return IPlugin::contextMenuExtension( context ); // // KDevelop::ProjectItemContext* ctx = dynamic_cast( context ); // QList items = ctx->items(); // // if( items.isEmpty() ) // return IPlugin::contextMenuExtension( context ); // // m_clickedItems = items; // ContextMenuExtension menuExt; // if(items.count()==1 && dynamic_cast(items.first())) // { // QAction * action = new QAction( i18n( "Jump to Target Definition" ), this ); // connect( action, SIGNAL(triggered()), this, SLOT(jumpToDeclaration()) ); // menuExt.addAction( ContextMenuExtension::ProjectGroup, action ); // } // // return menuExt; // } // // void CMakeManager::jumpToDeclaration() // { // DUChainAttatched* du=dynamic_cast(m_clickedItems.first()); // if(du) // { // KTextEditor::Cursor c; // QUrl url; // { // KDevelop::DUChainReadLocker lock; // Declaration* decl = du->declaration().data(); // if(!decl) // return; // c = decl->rangeInCurrentRevision().start(); // url = decl->url().toUrl(); // } // // ICore::self()->documentController()->openDocument(url, c); // } // } // // // TODO: Port to Path API // bool CMakeManager::moveFilesAndFolders(const QList< ProjectBaseItem* > &items, ProjectFolderItem* toFolder) // { // using namespace CMakeEdit; // // ApplyChangesWidget changesWidget; // changesWidget.setCaption(DIALOG_CAPTION); // changesWidget.setInformation(i18n("Move files and folders within CMakeLists as follows:")); // // bool cmakeSuccessful = true; // CMakeFolderItem *nearestCMakeFolderItem = nearestCMakeFolder(toFolder); // IProject* project=toFolder->project(); // // QList movedUrls; // QList oldUrls; // foreach(ProjectBaseItem *movedItem, items) // { // QList dirtyItems = cmakeListedItemsAffectedByUrlChange(project, movedItem->url()); // QUrl movedItemNewUrl = toFolder->url(); // movedItemNewUrl.addPath(movedItem->baseName()); // if (movedItem->folder()) // movedItemNewUrl.adjustPath(QUrl::AddTrailingSlash); // foreach(ProjectBaseItem* dirtyItem, dirtyItems) // { // QUrl dirtyItemNewUrl = afterMoveUrl(dirtyItem->url(), movedItem->url(), movedItemNewUrl); // if (CMakeFolderItem* folder = dynamic_cast(dirtyItem)) // { // cmakeSuccessful &= changesWidgetRemoveCMakeFolder(folder, &changesWidget); // cmakeSuccessful &= changesWidgetAddFolder(dirtyItemNewUrl, nearestCMakeFolderItem, &changesWidget); // } // else if (dirtyItem->parent()->target()) // { // cmakeSuccessful &= changesWidgetMoveTargetFile(dirtyItem, dirtyItemNewUrl, &changesWidget); // } // } // // oldUrls += movedItem->url(); // movedUrls += movedItemNewUrl; // } // // if (changesWidget.hasDocuments() && cmakeSuccessful) // cmakeSuccessful &= changesWidget.exec() && changesWidget.applyAllChanges(); // // if (!cmakeSuccessful) // { // if (KMessageBox::questionYesNo( QApplication::activeWindow(), // i18n("Changes to CMakeLists failed, abort move?"), // DIALOG_CAPTION ) == KMessageBox::Yes) // return false; // } // // QList::const_iterator it1=oldUrls.constBegin(), it1End=oldUrls.constEnd(); // QList::const_iterator it2=movedUrls.constBegin(); // Q_ASSERT(oldUrls.size()==movedUrls.size()); // for(; it1!=it1End; ++it1, ++it2) // { // if (!KDevelop::renameUrl(project, *it1, *it2)) // return false; // // QList renamedItems = project->itemsForUrl(*it2); // bool dir = QFileInfo(it2->toLocalFile()).isDir(); // foreach(ProjectBaseItem* item, renamedItems) { // if(dir) // emit folderRenamed(Path(*it1), item->folder()); // else // emit fileRenamed(Path(*it1), item->file()); // } // } // // return true; // } // // bool CMakeManager::copyFilesAndFolders(const KDevelop::Path::List &items, KDevelop::ProjectFolderItem* toFolder) // { // IProject* project = toFolder->project(); // foreach(const Path& path, items) { // if (!KDevelop::copyUrl(project, path.toUrl(), toFolder->url())) // return false; // } // // return true; // } // // bool CMakeManager::removeFilesAndFolders(const QList &items) // { // using namespace CMakeEdit; // // IProject* p = 0; // QList urls; // foreach(ProjectBaseItem* item, items) // { // Q_ASSERT(item->folder() || item->file()); // // urls += item->url(); // if(!p) // p = item->project(); // } // // //First do CMakeLists changes // ApplyChangesWidget changesWidget; // changesWidget.setCaption(DIALOG_CAPTION); // changesWidget.setInformation(i18n("Remove files and folders from CMakeLists as follows:")); // // bool cmakeSuccessful = changesWidgetRemoveItems(cmakeListedItemsAffectedByItemsChanged(items).toSet(), &changesWidget); // // if (changesWidget.hasDocuments() && cmakeSuccessful) // cmakeSuccessful &= changesWidget.exec() && changesWidget.applyAllChanges(); // // if (!cmakeSuccessful) // { // if (KMessageBox::questionYesNo( QApplication::activeWindow(), // i18n("Changes to CMakeLists failed, abort deletion?"), // DIALOG_CAPTION ) == KMessageBox::Yes) // return false; // } // // bool ret = true; // //Then delete the files/folders // foreach(const QUrl& file, urls) // { // ret &= KDevelop::removeUrl(p, file, QDir(file.toLocalFile()).exists()); // } // // return ret; // } bool CMakeManager::removeFilesFromTargets(const QList &/*files*/) { // using namespace CMakeEdit; // // ApplyChangesWidget changesWidget; // changesWidget.setCaption(DIALOG_CAPTION); // changesWidget.setInformation(i18n("Modify project targets as follows:")); // // if (!files.isEmpty() && // changesWidgetRemoveFilesFromTargets(files, &changesWidget) && // changesWidget.exec() && // changesWidget.applyAllChanges()) { // return true; // } return false; } // ProjectFolderItem* CMakeManager::addFolder(const Path& folder, ProjectFolderItem* parent) // { // using namespace CMakeEdit; // // CMakeFolderItem *cmakeParent = nearestCMakeFolder(parent); // if(!cmakeParent) // return 0; // // ApplyChangesWidget changesWidget; // changesWidget.setCaption(DIALOG_CAPTION); // changesWidget.setInformation(i18n("Create folder '%1':", folder.lastPathSegment())); // // ///FIXME: use path in changes widget // changesWidgetAddFolder(folder.toUrl(), cmakeParent, &changesWidget); // // if(changesWidget.exec() && changesWidget.applyAllChanges()) // { // if(KDevelop::createFolder(folder.toUrl())) { //If saved we create the folder then the CMakeLists.txt file // Path newCMakeLists(folder, "CMakeLists.txt"); // KDevelop::createFile( newCMakeLists.toUrl() ); // } else // KMessageBox::error(0, i18n("Could not save the change."), // DIALOG_CAPTION); // } // // return 0; // } // // KDevelop::ProjectFileItem* CMakeManager::addFile( const Path& file, KDevelop::ProjectFolderItem* parent) // { // KDevelop::ProjectFileItem* created = 0; // if ( KDevelop::createFile(file.toUrl()) ) { // QList< ProjectFileItem* > files = parent->project()->filesForPath(IndexedString(file.pathOrUrl())); // if(!files.isEmpty()) // created = files.first(); // else // created = new KDevelop::ProjectFileItem( parent->project(), file, parent ); // } // return created; // } bool CMakeManager::addFilesToTarget(const QList< ProjectFileItem* > &/*_files*/, ProjectTargetItem* /*target*/) { return false; // using namespace CMakeEdit; // // const QSet headerExt = QSet() << ".h" << ".hpp" << ".hxx"; // QList< ProjectFileItem* > files = _files; // for (int i = files.count() - 1; i >= 0; --i) // { // QString fileName = files[i]->fileName(); // QString fileExt = fileName.mid(fileName.lastIndexOf('.')); // QList sameUrlItems = files[i]->project()->itemsForUrl(files[i]->url()); // if (headerExt.contains(fileExt)) // files.removeAt(i); // else foreach(ProjectBaseItem* item, sameUrlItems) // { // if (item->parent() == target) // { // files.removeAt(i); // break; // } // } // } // // if(files.isEmpty()) // return true; // // ApplyChangesWidget changesWidget; // changesWidget.setCaption(DIALOG_CAPTION); // changesWidget.setInformation(i18n("Modify target '%1' as follows:", target->baseName())); // // bool success = changesWidgetAddFilesToTarget(files, target, &changesWidget) && // changesWidget.exec() && // changesWidget.applyAllChanges(); // // if(!success) // KMessageBox::error(0, i18n("CMakeLists changes failed."), DIALOG_CAPTION); // // return success; } // bool CMakeManager::renameFileOrFolder(ProjectBaseItem *item, const Path &newPath) // { // using namespace CMakeEdit; // // ApplyChangesWidget changesWidget; // changesWidget.setCaption(DIALOG_CAPTION); // changesWidget.setInformation(i18n("Rename '%1' to '%2':", item->text(), // newPath.lastPathSegment())); // // bool cmakeSuccessful = true, changedCMakeLists=false; // IProject* project=item->project(); // const Path oldPath=item->path(); // QUrl oldUrl=oldPath.toUrl(); // if (item->file()) // { // QList targetFiles = cmakeListedItemsAffectedByUrlChange(project, oldUrl); // foreach(ProjectBaseItem* targetFile, targetFiles) // ///FIXME: use path in changes widget // cmakeSuccessful &= changesWidgetMoveTargetFile(targetFile, newPath.toUrl(), &changesWidget); // } // else if (CMakeFolderItem *folder = dynamic_cast(item)) // ///FIXME: use path in changes widget // cmakeSuccessful &= changesWidgetRenameFolder(folder, newPath.toUrl(), &changesWidget); // // item->setPath(newPath); // if (changesWidget.hasDocuments() && cmakeSuccessful) { // changedCMakeLists = changesWidget.exec() && changesWidget.applyAllChanges(); // cmakeSuccessful &= changedCMakeLists; // } // // if (!cmakeSuccessful) // { // if (KMessageBox::questionYesNo( QApplication::activeWindow(), // i18n("Changes to CMakeLists failed, abort rename?"), // DIALOG_CAPTION ) == KMessageBox::Yes) // return false; // } // // bool ret = KDevelop::renameUrl(project, oldUrl, newPath.toUrl()); // if(!ret) { // item->setPath(oldPath); // } // return ret; // } // // bool CMakeManager::renameFile(ProjectFileItem *item, const Path &newPath) // { // return renameFileOrFolder(item, newPath); // } // // bool CMakeManager::renameFolder(ProjectFolderItem* item, const Path &newPath) // { // return renameFileOrFolder(item, newPath); // } QWidget* CMakeManager::specialLanguageObjectNavigationWidget(const QUrl &url, const KTextEditor::Cursor& position) { KDevelop::TopDUContextPointer top= TopDUContextPointer(KDevelop::DUChain::self()->chainForDocument(url)); Declaration *decl=0; if(top) { int useAt=top->findUseAt(top->transformToLocalRevision(position)); if(useAt>=0) { Use u=top->uses()[useAt]; decl=u.usedDeclaration(top->topContext()); } } CMakeNavigationWidget* doc=0; if(decl) { doc=new CMakeNavigationWidget(top, decl); } else { const IDocument* d=ICore::self()->documentController()->documentForUrl(url); const KTextEditor::Document* e=d->textDocument(); KTextEditor::Cursor start=position, end=position, step(0,1); for(QChar i=e->characterAt(start); i.isLetter() || i=='_'; i=e->characterAt(start-=step)) {} start+=step; for(QChar i=e->characterAt(end); i.isLetter() || i=='_'; i=e->characterAt(end+=step)) {} QString id=e->text(KTextEditor::Range(start, end)); ICMakeDocumentation* docu=CMake::cmakeDocumentation(); if( docu ) { IDocumentation::Ptr desc=docu->description(id, url); if(desc) { doc=new CMakeNavigationWidget(top, desc); } } } return doc; } QPair CMakeManager::cacheValue(KDevelop::IProject* /*project*/, const QString& /*id*/) const { return QPair(); } // { // QPair ret; // if(project==0 && !m_projectsData.isEmpty()) // { // project=m_projectsData.keys().first(); // } // // // qCDebug(CMAKE) << "cache value " << id << project << (m_projectsData.contains(project) && m_projectsData[project].cache.contains(id)); // CMakeProjectData* data = m_projectsData[project]; // if(data && data->cache.contains(id)) // { // const CacheEntry& e=data->cache.value(id); // ret.first=e.value; // ret.second=e.doc; // } // return ret; // }Add // void CMakeManager::projectClosing(IProject* p) { m_projects.remove(p); // delete m_projectsData.take(p); // delete m_watchers.take(p); // // m_filter->remove(p); // // qCDebug(CMAKE) << "Project closed" << p; } // // QStringList CMakeManager::processGeneratorExpression(const QStringList& expr, IProject* project, ProjectTargetItem* target) const // { // QStringList ret; // const CMakeProjectData* data = m_projectsData[project]; // GenerationExpressionSolver exec(data->properties, data->targetAlias); // if(target) // exec.setTargetName(target->text()); // // exec.defineVariable("INSTALL_PREFIX", data->vm.value("CMAKE_INSTALL_PREFIX").join(QString())); // for(QStringList::const_iterator it = expr.constBegin(), itEnd = expr.constEnd(); it!=itEnd; ++it) { // QStringList val = exec.run(*it).split(';'); // ret += val; // } // return ret; // } /* void CMakeManager::addPending(const Path& path, CMakeFolderItem* folder) { m_pending.insert(path, folder); } CMakeFolderItem* CMakeManager::takePending(const Path& path) { return m_pending.take(path); } void CMakeManager::addWatcher(IProject* p, const QString& path) { if (QFileSystemWatcher* watcher = m_watchers.value(p)) { watcher->addPath(path); } else { qWarning() << "Could not find a watcher for project" << p << p->name() << ", path " << path; Q_ASSERT(false); } }*/ // CMakeProjectData CMakeManager::projectData(IProject* project) // { // Q_ASSERT(QThread::currentThread() == project->thread()); // CMakeProjectData* data = m_projectsData[project]; // if(!data) { // data = new CMakeProjectData; // m_projectsData[project] = data; // } // return *data; // } ProjectFilterManager* CMakeManager::filterManager() const { return m_filter; } void CMakeManager::dirtyFile(const QString& path) { qCDebug(CMAKE) << "dirty!" << path; //we initialize again hte project that sent the signal for(QHash::const_iterator it = m_projects.constBegin(), itEnd = m_projects.constEnd(); it!=itEnd; ++it) { if(it->watcher == sender()) { reload(it.key()->projectItem()); break; } } } void CMakeManager::folderAdded(KDevelop::ProjectFolderItem* folder) { populateTargets(folder, m_projects[folder->project()].targets); } ProjectFolderItem* CMakeManager::createFolderItem(IProject* project, const Path& path, ProjectBaseItem* parent) { // TODO: when we have data about targets, use folders with targets or similar if (QFile::exists(path.toLocalFile()+"/CMakeLists.txt")) return new KDevelop::ProjectBuildFolderItem( project, path, parent ); else return KDevelop::AbstractFileManagerPlugin::createFolderItem(project, path, parent); } int CMakeManager::perProjectConfigPages() const { return 1; } ConfigPage* CMakeManager::perProjectConfigPage(int number, const ProjectConfigOptions& options, QWidget* parent) { if (number == 0) { return new CMakePreferences(this, options, parent); } return nullptr; } #include "cmakemanager.moc"