diff --git a/kdevplatform/language/duchain/stringhelpers.h b/kdevplatform/language/duchain/stringhelpers.h --- a/kdevplatform/language/duchain/stringhelpers.h +++ b/kdevplatform/language/duchain/stringhelpers.h @@ -101,6 +101,11 @@ */ QString KDEVPLATFORMLANGUAGE_EXPORT removeWhitespace(const QString& str); +/** + * Returns the first index after a comment block + */ +int KDEVPLATFORMLANGUAGE_EXPORT skipCommentBlock(const QString& str); + /** * Can be used to iterate through different kinds of parameters, for example template-parameters */ diff --git a/plugins/clang/CMakeLists.txt b/plugins/clang/CMakeLists.txt --- a/plugins/clang/CMakeLists.txt +++ b/plugins/clang/CMakeLists.txt @@ -72,6 +72,7 @@ duchain/types/classspecializationtype.cpp duchain/unknowndeclarationproblem.cpp duchain/unsavedfile.cpp + duchain/headerguardassistant.cpp util/clangdebug.cpp util/clangtypes.cpp diff --git a/plugins/clang/duchain/headerguardassistant.h b/plugins/clang/duchain/headerguardassistant.h new file mode 100644 --- /dev/null +++ b/plugins/clang/duchain/headerguardassistant.h @@ -0,0 +1,44 @@ +/* + * Copyright 2018 Amish K. Naidu + * + * 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 HEADERGUARDASSISTANT_H +#define HEADERGUARDASSISTANT_H + +#include + +#include + +class HeaderGuardAssistant + : public KDevelop::IAssistant +{ +public: + HeaderGuardAssistant(const CXTranslationUnit unit, const CXFile file); + virtual ~HeaderGuardAssistant() override = default; + + QString title() const override; + + void createActions() override; + +private: + const CXTranslationUnit m_unit; + const CXFile m_file; +}; + +#endif // HEADERGUARDASSISTANT_H diff --git a/plugins/clang/duchain/headerguardassistant.cpp b/plugins/clang/duchain/headerguardassistant.cpp new file mode 100644 --- /dev/null +++ b/plugins/clang/duchain/headerguardassistant.cpp @@ -0,0 +1,126 @@ +/* + * Copyright 2018 Amish K. Naidu + * + * 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 "headerguardassistant.h" +#include "util/clangutils.h" +#include "util/clangtypes.h" + +#include +#include + +#include +#include + +#include + + +enum class GuardType +{ + Pragma, + Macro +}; + +class AddHeaderGuardAction + : public KDevelop::IAssistantAction +{ +public: + AddHeaderGuardAction(const GuardType type, const CXTranslationUnit unit, const CXFile file) + : m_type(type) + , m_unit(unit) + , m_file(file) + { + } + + virtual ~AddHeaderGuardAction() override = default; + + QString description() const override + { + switch (m_type) { + case GuardType::Pragma: return i18n("Add #pragma once"); + case GuardType::Macro: return i18n("Add macro-based #ifndef/#define/#endif heard guard"); + } + return {}; + } + + void execute() override + { + // skip license etc + const int line = std::max(static_cast(ClangUtils::skipTopCommentBlock(m_unit, m_file)) - 1, 0); + + KDevelop::DocumentChangeSet changes; + // duchain lock ? + const KDevelop::IndexedString path {QDir(ClangString(clang_getFileName(m_file)).toString()).canonicalPath()}; + + switch (m_type) { + case GuardType::Pragma: + { + KDevelop::DocumentChange change(path, KTextEditor::Range(line, 0, line, 0), QString(), + QStringLiteral("#pragma once\n\n")); + changes.addChange(change); + break; + } + case GuardType::Macro: + { + const QString macro = path.toUrl() + .fileName(QUrl::PrettyDecoded) + .replace(QRegularExpression(QStringLiteral("[^a-zA-Z0-9]")), QLatin1String(" ")) + .simplified() + .toUpper() + .replace(QLatin1Char(' '), QLatin1Char('_')) + .append(QLatin1String("_INCLUDED")); + + changes.addChange(KDevelop::DocumentChange(path, KTextEditor::Range(line, 0, line, 0), QString(), + QStringLiteral("#ifndef %1\n#define %1\n\n").arg(macro))); + + const auto representation = KDevelop::createCodeRepresentation(path); + const int lastLine = representation->lines() - 1; + changes.addChange(KDevelop::DocumentChange(path, KTextEditor::Range(lastLine, 0, lastLine, 0), QString(), + QStringLiteral("#endif // %1").arg(macro))); + break; + } + } + + changes.setReplacementPolicy(KDevelop::DocumentChangeSet::WarnOnFailedChange); + changes.applyAllChanges(); + emit executed(this); + } + +private: + const GuardType m_type; + const CXTranslationUnit m_unit; + const CXFile m_file; +}; + +HeaderGuardAssistant::HeaderGuardAssistant(const CXTranslationUnit unit, const CXFile file) + : m_unit(unit) + , m_file(file) +{ +} + +QString HeaderGuardAssistant::title() const +{ + return QStringLiteral("Fix-Header"); +} + +void HeaderGuardAssistant::createActions() +{ + addAction(KDevelop::IAssistantAction::Ptr{new AddHeaderGuardAction(GuardType::Pragma, m_unit, m_file)}); + addAction(KDevelop::IAssistantAction::Ptr{new AddHeaderGuardAction(GuardType::Macro, m_unit, m_file)}); +} diff --git a/plugins/clang/duchain/parsesession.cpp b/plugins/clang/duchain/parsesession.cpp --- a/plugins/clang/duchain/parsesession.cpp +++ b/plugins/clang/duchain/parsesession.cpp @@ -32,9 +32,11 @@ #include "util/clangdebug.h" #include "util/clangtypes.h" #include "util/clangutils.h" +#include "headerguardassistant.h" #include #include +#include #include @@ -144,7 +146,7 @@ if (url.isEmpty()) { continue; } - + QFileInfo info(url.toLocalFile()); QByteArray path = url.toLocalFile().toUtf8(); @@ -475,15 +477,16 @@ if (ClangHelpers::isHeader(path) && !clang_isFileMultipleIncludeGuarded(unit(), file) && !clang_Location_isInSystemHeader(clang_getLocationForOffset(d->m_unit, file, 0))) { - ProblemPointer problem(new Problem); + QExplicitlySharedDataPointer problem(new StaticAssistantProblem); 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()}); + const KTextEditor::Range problemRange(0, 0, KDevelop::createCodeRepresentation(indexedPath)->lines(), 0); + problem->setFinalLocation(DocumentRange{indexedPath, problemRange}); problem->setSource(IProblem::Preprocessor); + problem->setSolutionAssistant(KDevelop::IAssistant::Ptr(new HeaderGuardAssistant(d->m_unit, file))); problems << problem; - // TODO: Easy to add an assistant here that adds the guards -- any takers? } #endif diff --git a/plugins/clang/util/clangutils.h b/plugins/clang/util/clangutils.h --- a/plugins/clang/util/clangutils.h +++ b/plugins/clang/util/clangutils.h @@ -167,6 +167,11 @@ * Returns special attributes (isFinal, isQtSlot, ...) given a @p cursor representing a CXXmethod */ KDevelop::ClassFunctionFlags specialAttributes(CXCursor cursor); + + /** + * @return the top most line in a file skipping any comment block + */ + unsigned int skipTopCommentBlock(CXTranslationUnit unit, CXFile file); } #endif // CLANGUTILS_H diff --git a/plugins/clang/util/clangutils.cpp b/plugins/clang/util/clangutils.cpp --- a/plugins/clang/util/clangutils.cpp +++ b/plugins/clang/util/clangutils.cpp @@ -35,6 +35,9 @@ #include #include +#include +#include + using namespace KDevelop; CXCursor ClangUtils::getCXCursor(int line, int column, const CXTranslationUnit& unit, const CXFile& file) @@ -458,3 +461,21 @@ } return flags; } + +unsigned int ClangUtils::skipTopCommentBlock(CXTranslationUnit unit, CXFile file) +{ + auto nextTokenLocation = clang_getLocation(unit, file, 1, 1); + std::unique_ptr> token ( + clang_getToken(unit, nextTokenLocation), + [&](CXToken* token) { clang_disposeTokens(unit, token, 1); } + ); + + while (token && clang_getTokenKind(*token) == CXToken_Comment){ + token.reset(clang_getToken(unit, nextTokenLocation)); + nextTokenLocation = ClangRange(clang_getTokenExtent(unit, *token)).end(); + } + + unsigned int line; + clang_getExpansionLocation(nextTokenLocation, &file, &line, nullptr, nullptr); + return line; +}