File indexing completed on 2024-06-09 04:36:31

0001 /*************************************************************************************
0002  *  Copyright (C) 2016 by Carlos Nihelton <carlosnsoliveira@gmail.com>               *
0003  *                                                                                   *
0004  *  This program is free software; you can redistribute it and/or                    *
0005  *  modify it under the terms of the GNU General Public License                      *
0006  *  as published by the Free Software Foundation; either version 2                   *
0007  *  of the License, or (at your option) any later version.                           *
0008  *                                                                                   *
0009  *  This program is distributed in the hope that it will be useful,                  *
0010  *  but WITHOUT ANY WARRANTY; without even the implied warranty of                   *
0011  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                    *
0012  *  GNU General Public License for more details.                                     *
0013  *                                                                                   *
0014  *  You should have received a copy of the GNU General Public License                *
0015  *  along with this program; if not, write to the Free Software                      *
0016  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA   *
0017  *************************************************************************************/
0018 
0019 #include <unistd.h>
0020 
0021 #include <QAction>
0022 #include <QMessageBox>
0023 
0024 #include <kactioncollection.h>
0025 #include <klocalizedstring.h>
0026 #include <kpluginfactory.h>
0027 #include <kpluginloader.h>
0028 #include <kprocess.h>
0029 
0030 #include <execute/iexecuteplugin.h>
0031 
0032 #include <KXMLGUIFactory>
0033 #include <interfaces/icore.h>
0034 #include <interfaces/idebugcontroller.h>
0035 #include <interfaces/idocument.h>
0036 #include <interfaces/idocumentcontroller.h>
0037 #include <interfaces/ilanguagecontroller.h>
0038 #include <interfaces/iplugincontroller.h>
0039 #include <interfaces/iproject.h>
0040 #include <interfaces/iprojectcontroller.h>
0041 #include <interfaces/iruncontroller.h>
0042 #include <interfaces/iuicontroller.h>
0043 #include <interfaces/launchconfigurationtype.h>
0044 #include <language/interfaces/editorcontext.h>
0045 #include <project/interfaces/ibuildsystemmanager.h>
0046 #include <project/projectconfigpage.h>
0047 #include <project/projectmodel.h>
0048 #include <shell/problemmodelset.h>
0049 #include <util/executecompositejob.h>
0050 
0051 #include "./config/clangtidypreferences.h"
0052 #include "./config/perprojectconfigpage.h"
0053 #include "debug.h"
0054 #include "job.h"
0055 #include "plugin.h"
0056 
0057 using namespace KDevelop;
0058 
0059 K_PLUGIN_FACTORY_WITH_JSON(ClangTidyFactory, "res/kdevclangtidy.json", registerPlugin<ClangTidy::Plugin>();)
0060 namespace ClangTidy
0061 {
0062 Plugin::Plugin(QObject* parent, const QVariantList& /*unused*/)
0063     : IPlugin("kdevclangtidy", parent)
0064     , m_model(new KDevelop::ProblemModel(parent))
0065 {
0066     qCDebug(KDEV_CLANGTIDY) << "setting clangtidy rc file";
0067     setXMLFile("kdevclangtidy.rc");
0068 
0069     QAction* act_checkfile;
0070     act_checkfile = actionCollection()->addAction("clangtidy_file", this, SLOT(runClangTidyFile()));
0071     act_checkfile->setStatusTip(i18n("Launches ClangTidy for current file"));
0072     act_checkfile->setText(i18n("clang-tidy"));
0073 
0074     /*     TODO: Uncomment this only when discover a safe way to run clang-tidy on the whole project.
0075     //     QAction* act_check_all_files;
0076     //     act_check_all_files = actionCollection()->addAction ( "clangtidy_all", this, SLOT ( runClangTidyAll() ) );
0077     //     act_check_all_files->setStatusTip ( i18n ( "Launches clangtidy for all translation "
0078     //                                         "units of current project" ) );
0079     //     act_check_all_files->setText ( i18n ( "clang-tidy (all)" ) );
0080     */
0081 
0082     IExecutePlugin* iface = KDevelop::ICore::self()
0083                                 ->pluginController()
0084                                 ->pluginForExtension("org.kdevelop.IExecutePlugin")
0085                                 ->extension<IExecutePlugin>();
0086     Q_ASSERT(iface);
0087 
0088     ProblemModelSet* pms = core()->languageController()->problemModelSet();
0089     pms->addModel(QStringLiteral("ClangTidy"), m_model.data());
0090 
0091     m_config = KSharedConfig::openConfig()->group("ClangTidy");
0092     auto clangtidyPath = m_config.readEntry(ConfigGroup::ExecutablePath);
0093 
0094     // TODO(cnihelton): auto detect clang-tidy executable instead of hard-coding it.
0095     if (clangtidyPath.isEmpty()) {
0096         clangtidyPath = QString("/usr/bin/clang-tidy");
0097     }
0098 
0099     collectAllAvailableChecks(clangtidyPath);
0100 
0101     m_config.writeEntry(ConfigGroup::AdditionalParameters, "");
0102     for (auto check : m_allChecks) {
0103         bool enable = check.contains("cert") || check.contains("-core.") || check.contains("-cplusplus")
0104             || check.contains("-deadcode") || check.contains("-security") || check.contains("cppcoreguide");
0105         if (enable) {
0106             m_activeChecks << check;
0107         } else {
0108             m_activeChecks.removeAll(check);
0109         }
0110     }
0111     m_activeChecks.removeDuplicates();
0112     m_config.writeEntry(ConfigGroup::EnabledChecks, m_activeChecks.join(','));
0113 }
0114 
0115 void Plugin::unload()
0116 {
0117     ProblemModelSet* pms = core()->languageController()->problemModelSet();
0118     pms->removeModel(QStringLiteral("ClangTidy"));
0119 }
0120 
0121 void Plugin::collectAllAvailableChecks(QString clangtidyPath)
0122 {
0123     m_allChecks.clear();
0124     KProcess tidy;
0125     tidy << clangtidyPath << QLatin1String("-checks=*") << QLatin1String("--list-checks");
0126     tidy.setOutputChannelMode(KProcess::OnlyStdoutChannel);
0127     tidy.start();
0128 
0129     if (!tidy.waitForStarted()) {
0130         qCDebug(KDEV_CLANGTIDY) << "Unable to execute clang-tidy.";
0131         return;
0132     }
0133 
0134     tidy.closeWriteChannel();
0135     if (!tidy.waitForFinished()) {
0136         qCDebug(KDEV_CLANGTIDY) << "Failed during clang-tidy execution.";
0137         return;
0138     }
0139 
0140     QTextStream ios(&tidy);
0141     QString each;
0142     while (ios.readLineInto(&each)) {
0143         m_allChecks.append(each.trimmed());
0144     }
0145     if (m_allChecks.size() > 3) {
0146         m_allChecks.removeAt(m_allChecks.length() - 1);
0147         m_allChecks.removeAt(0);
0148     }
0149     m_allChecks.removeDuplicates();
0150 }
0151 
0152 void Plugin::runClangTidy(bool allFiles)
0153 {
0154     KDevelop::IDocument* doc = core()->documentController()->activeDocument();
0155     if (!doc) {
0156         QMessageBox::critical(nullptr, i18n("Error starting clang-tidy"),
0157                               i18n("No suitable active file, unable to deduce project."));
0158         return;
0159     }
0160 
0161     KDevelop::IProject* project = core()->projectController()->findProjectForUrl(doc->url());
0162     if (!project) {
0163         QMessageBox::critical(nullptr, i18n("Error starting clang-tidy"), i18n("Active file isn't in a project"));
0164         return;
0165     }
0166 
0167     m_config = project->projectConfiguration()->group("ClangTidy");
0168     if (!m_config.isValid()) {
0169         QMessageBox::critical(nullptr, i18n("Error starting ClangTidy"),
0170                               i18n("Can't load parameters. They must be set in the project settings."));
0171         return;
0172     }
0173 
0174     auto clangtidyPath = m_config.readEntry(ConfigGroup::ExecutablePath);
0175     auto buildSystem = project->buildSystemManager();
0176 
0177     Job::Parameters params;
0178 
0179     params.projectRootDir = project->path().toLocalFile();
0180 
0181     // TODO: auto detect clang-tidy executable instead of hard-coding it.
0182     if (clangtidyPath.isEmpty()) {
0183         params.executablePath = QStringLiteral("/usr/bin/clang-tidy");
0184     } else {
0185         params.executablePath = clangtidyPath;
0186     }
0187 
0188     if (allFiles) {
0189         params.filePath = project->path().toUrl().toLocalFile();
0190     } else {
0191         params.filePath = doc->url().toLocalFile();
0192     }
0193     params.buildDir = buildSystem->buildDirectory(project->projectItem()).toLocalFile();
0194     params.additionalParameters = m_config.readEntry(ConfigGroup::AdditionalParameters);
0195     params.analiseTempDtors = m_config.readEntry(ConfigGroup::AnaliseTempDtors);
0196     params.enabledChecks = m_activeChecks.join(',');
0197     params.useConfigFile = m_config.readEntry(ConfigGroup::UseConfigFile);
0198     params.dumpConfig = m_config.readEntry(ConfigGroup::DumpConfig);
0199     params.enableChecksProfile = m_config.readEntry(ConfigGroup::EnableChecksProfile);
0200     params.exportFixes = m_config.readEntry(ConfigGroup::ExportFixes);
0201     params.extraArgs = m_config.readEntry(ConfigGroup::ExtraArgs);
0202     params.extraArgsBefore = m_config.readEntry(ConfigGroup::ExtraArgsBefore);
0203     params.autoFix = m_config.readEntry(ConfigGroup::AutoFix);
0204     params.headerFilter = m_config.readEntry(ConfigGroup::HeaderFilter);
0205     params.lineFilter = m_config.readEntry(ConfigGroup::LineFilter);
0206     params.listChecks = m_config.readEntry(ConfigGroup::ListChecks);
0207     params.checkSystemHeaders = m_config.readEntry(ConfigGroup::CheckSystemHeaders);
0208 
0209     if (!params.dumpConfig.isEmpty()) {
0210         Job* job = new ClangTidy::Job(params, this);
0211         core()->runController()->registerJob(job);
0212         params.dumpConfig = QString();
0213     }
0214     Job* job2 = new ClangTidy::Job(params, this);
0215     connect(job2, SIGNAL(finished(KJob*)), this, SLOT(result(KJob*)));
0216     core()->runController()->registerJob(job2);
0217 }
0218 
0219 void Plugin::runClangTidyFile()
0220 {
0221     bool allFiles = false;
0222     runClangTidy(allFiles);
0223 }
0224 
0225 void Plugin::runClangTidyAll()
0226 {
0227     bool allFiles = true;
0228     runClangTidy(allFiles);
0229 }
0230 
0231 void Plugin::loadOutput()
0232 {
0233 }
0234 
0235 void Plugin::result(KJob* job)
0236 {
0237     Job* aj = dynamic_cast<Job*>(job);
0238     if (!aj) {
0239         return;
0240     }
0241 
0242     if (aj->status() == KDevelop::OutputExecuteJob::JobStatus::JobSucceeded) {
0243         m_model->setProblems(aj->problems());
0244 
0245         core()->uiController()->findToolView(i18nd("kdevproblemreporter", "Problems"), 0,
0246                                              KDevelop::IUiController::FindFlags::Raise);
0247     }
0248 }
0249 
0250 KDevelop::ContextMenuExtension Plugin::contextMenuExtension(KDevelop::Context* context)
0251 {
0252     KDevelop::IDocument* doc = core()->documentController()->activeDocument();
0253     KDevelop::ContextMenuExtension extension = KDevelop::IPlugin::contextMenuExtension(context);
0254 
0255     if (context->type() == KDevelop::Context::EditorContext) {
0256 
0257         auto mime = doc->mimeType().name();
0258         if (mime == QLatin1String("text/x-c++src") || mime == QLatin1String("text/x-csrc")) {
0259             QAction* action
0260                 = new QAction(QIcon::fromTheme("document-new"), i18n("Check current unit with clang-tidy"), this);
0261             connect(action, SIGNAL(triggered(bool)), this, SLOT(runClangTidyFile()));
0262             extension.addAction(KDevelop::ContextMenuExtension::AnalyzeGroup, action);
0263         }
0264     }
0265     return extension;
0266 }
0267 
0268 KDevelop::ConfigPage* Plugin::perProjectConfigPage(int number, const ProjectConfigOptions& options, QWidget* parent)
0269 {
0270     if (number != 0) {
0271         return nullptr;
0272     } else {
0273         auto config = new PerProjectConfigPage(options.project, parent);
0274         config->setActiveChecksReceptorList(&m_activeChecks);
0275         config->setList(m_allChecks);
0276         return config;
0277     }
0278 }
0279 
0280 KDevelop::ConfigPage* Plugin::configPage(int number, QWidget* parent)
0281 {
0282     if (number != 0) {
0283         return nullptr;
0284     } else {
0285         return new ClangTidyPreferences(this, parent);
0286     }
0287 }
0288 }
0289 
0290 #include "plugin.moc"