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"