1 /*************************************************************************************
2  *  Copyright (C) 2016 by Carlos Nihelton <carlosnsoliveira@gmail.com>               *
3  *                                                                                   *
4  *  This program is free software; you can redistribute it and/or                    *
5  *  modify it under the terms of the GNU General Public License                      *
6  *  as published by the Free Software Foundation; either version 2                   *
7  *  of the License, or (at your option) any later version.                           *
8  *                                                                                   *
9  *  This program is distributed in the hope that it will be useful,                  *
10  *  but WITHOUT ANY WARRANTY; without even the implied warranty of                   *
11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                    *
12  *  GNU General Public License for more details.                                     *
13  *                                                                                   *
14  *  You should have received a copy of the GNU General Public License                *
15  *  along with this program; if not, write to the Free Software                      *
16  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA   *
17  *                                                                                   *
18  *  Now let's read a beautiful song:                                                 *
19 
20                 夢のつづき
21                 追いかけていたはずなのに
22 
23                 曲がりくねった
24                 細い道 人につまずく
25 
26                 あの頃みたいにって
27                 戻りたい訳じゃないの
28 
29                 無くしてきた空を
30                 探してる
31 
32                 わかってくれますように
33 
34                 犠牲になったような
35                 悲しい顔はやめてよ
36 
37                 罪の最後は涙じゃないよ
38                 ずっと苦しく背負ってくんだ
39 
40                 出口見えない感情迷路に
41                 誰を待ってるの?
42 
43                 白いノートに綴ったように
44                 もっと素直に吐き出したいよ
45 
46                 何から
47                 逃れたいんだ
48 
49                 …現実ってやつ?
50 
51                 叶えるために
52                 生きてるんだって
53 
54                 忘れちゃいそうな
55                 夜の真ん中
56 
57                 無難になんて
58                 やってられないから
59 
60                 …帰る場所もないの
61 
62                 この想いを 消してしまうには
63                 まだ人生長いでしょ?(I'm on the way)
64 
65                 懐かしくなる
66                 こんな痛みも歓迎じゃん
67 
68                 謝らなくちゃいけないよね
69                 ah ごめんね
70 
71                 うまく言えなくて
72                 心配かけたままだったね
73 
74                 あの日かかえた全部
75                 あしたかかえる全部
76 
77                 順番つけたりは
78                 しないから
79 
80                 わかってくれますように
81 
82                 そっと目を閉じたんだ
83                 見たくないものまで
84                 見えんだもん
85 
86                 いらないウワサにちょっと
87                 初めて聞く発言どっち?
88 
89                 2回会ったら友達だって??
90                 ウソはやめてね
91 
92                 赤いハートが苛立つように
93                 身体ん中燃えているんだ
94 
95                 ホントは
96                 期待してんの
97 
98                 …現実ってやつ?
99 
100                 叶えるために
101                 生きてるんだって
102 
103                 叫びたくなるよ
104                 聞こえていますか?
105 
106                 無難になんて
107                 やってられないから
108 
109                 …帰る場所もないの
110 
111                 優しさには いつも感謝してる
112                 だから強くなりたい(I'm on the way)
113 
114                 進むために
115                 敵も味方も歓迎じゃん
116 
117                 どうやって次のドア
118                 開けるんだっけ?考えてる?
119 
120                 もう引き返せない
121                 物語 始まってるんだ
122 
123                 目を覚ませ 目を覚ませ
124 
125                 この想いを 消してしまうには
126                 まだ人生長いでしょ?
127 
128                 やり残してるコト
129                 やり直してみたいから
130 
131                 もう一度ゆこう
132 
133                 叶えるために
134                 生きてるんだって
135 
136                 叫びたくなるよ
137                 聞こえていますか?
138 
139                 無難になんて
140                 やってられないから
141 
142                 …帰る場所もないの
143 
144                 優しさには いつも感謝してる
145                 だから強くなりたい(I'm on the way)
146 
147                 懐かしくなる
148                 こんな痛みも歓迎じゃん
149 
150  *************************************************************************************/
151 
152 #include <unistd.h>
153 
154 #include <QAction>
155 #include <QMessageBox>
156 
157 #include <kactioncollection.h>
158 #include <klocalizedstring.h>
159 #include <kpluginfactory.h>
160 #include <kpluginloader.h>
161 #include <kprocess.h>
162 
163 #include <execute/iexecuteplugin.h>
164 
165 #include <KXMLGUIFactory>
166 #include <interfaces/icore.h>
167 #include <interfaces/idebugcontroller.h>
168 #include <interfaces/idocument.h>
169 #include <interfaces/idocumentcontroller.h>
170 #include <interfaces/ilanguagecontroller.h>
171 #include <interfaces/iplugincontroller.h>
172 #include <interfaces/iproject.h>
173 #include <interfaces/iprojectcontroller.h>
174 #include <interfaces/iruncontroller.h>
175 #include <interfaces/iuicontroller.h>
176 #include <interfaces/launchconfigurationtype.h>
177 #include <language/interfaces/editorcontext.h>
178 #include <project/interfaces/ibuildsystemmanager.h>
179 #include <project/projectconfigpage.h>
180 #include <project/projectmodel.h>
181 #include <shell/problemmodelset.h>
182 #include <util/executecompositejob.h>
183 
184 #include "./config/clangtidypreferences.h"
185 #include "./config/perprojectconfigpage.h"
186 #include "debug.h"
187 #include "job.h"
188 #include "plugin.h"
189 
190 using namespace KDevelop;
191 
192 K_PLUGIN_FACTORY_WITH_JSON(ClangTidyFactory, "res/kdevclangtidy.json", registerPlugin<ClangTidy::Plugin>();)
193 namespace ClangTidy
194 {
Plugin(QObject * parent,const QVariantList &)195 Plugin::Plugin(QObject* parent, const QVariantList& /*unused*/)
196     : IPlugin("kdevclangtidy", parent)
197     , m_model(new KDevelop::ProblemModel(parent))
198 {
199     qCDebug(KDEV_CLANGTIDY) << "setting clangtidy rc file";
200     setXMLFile("kdevclangtidy.rc");
201 
202     QAction* act_checkfile;
203     act_checkfile = actionCollection()->addAction("clangtidy_file", this, SLOT(runClangTidyFile()));
204     act_checkfile->setStatusTip(i18n("Launches ClangTidy for current file"));
205     act_checkfile->setText(i18n("clang-tidy"));
206 
207     /*     TODO: Uncomment this only when discover a safe way to run clang-tidy on the whole project.
208     //     QAction* act_check_all_files;
209     //     act_check_all_files = actionCollection()->addAction ( "clangtidy_all", this, SLOT ( runClangTidyAll() ) );
210     //     act_check_all_files->setStatusTip ( i18n ( "Launches clangtidy for all translation "
211     //                                         "units of current project" ) );
212     //     act_check_all_files->setText ( i18n ( "clang-tidy (all)" ) );
213     */
214 
215     IExecutePlugin* iface = KDevelop::ICore::self()
216                                 ->pluginController()
217                                 ->pluginForExtension("org.kdevelop.IExecutePlugin")
218                                 ->extension<IExecutePlugin>();
219     Q_ASSERT(iface);
220 
221     ProblemModelSet* pms = core()->languageController()->problemModelSet();
222     pms->addModel(QStringLiteral("ClangTidy"), m_model.data());
223 
224     m_config = KSharedConfig::openConfig()->group("ClangTidy");
225     auto clangtidyPath = m_config.readEntry(ConfigGroup::ExecutablePath);
226 
227     // TODO(cnihelton): auto detect clang-tidy executable instead of hard-coding it.
228     if (clangtidyPath.isEmpty()) {
229         clangtidyPath = QString("/usr/bin/clang-tidy");
230     }
231 
232     collectAllAvailableChecks(clangtidyPath);
233 
234     m_config.writeEntry(ConfigGroup::AdditionalParameters, "");
235     for (auto check : m_allChecks) {
236         bool enable = check.contains("cert") || check.contains("-core.") || check.contains("-cplusplus")
237             || check.contains("-deadcode") || check.contains("-security") || check.contains("cppcoreguide");
238         if (enable) {
239             m_activeChecks << check;
240         } else {
241             m_activeChecks.removeAll(check);
242         }
243     }
244     m_activeChecks.removeDuplicates();
245     m_config.writeEntry(ConfigGroup::EnabledChecks, m_activeChecks.join(','));
246 }
247 
unload()248 void Plugin::unload()
249 {
250     ProblemModelSet* pms = core()->languageController()->problemModelSet();
251     pms->removeModel(QStringLiteral("ClangTidy"));
252 }
253 
collectAllAvailableChecks(QString clangtidyPath)254 void Plugin::collectAllAvailableChecks(QString clangtidyPath)
255 {
256     m_allChecks.clear();
257     KProcess tidy;
258     tidy << clangtidyPath << QLatin1String("-checks=*") << QLatin1String("--list-checks");
259     tidy.setOutputChannelMode(KProcess::OnlyStdoutChannel);
260     tidy.start();
261 
262     if (!tidy.waitForStarted()) {
263         qCDebug(KDEV_CLANGTIDY) << "Unable to execute clang-tidy.";
264         return;
265     }
266 
267     tidy.closeWriteChannel();
268     if (!tidy.waitForFinished()) {
269         qCDebug(KDEV_CLANGTIDY) << "Failed during clang-tidy execution.";
270         return;
271     }
272 
273     QTextStream ios(&tidy);
274     QString each;
275     while (ios.readLineInto(&each)) {
276         m_allChecks.append(each.trimmed());
277     }
278     if (m_allChecks.size() > 3) {
279         m_allChecks.removeAt(m_allChecks.length() - 1);
280         m_allChecks.removeAt(0);
281     }
282     m_allChecks.removeDuplicates();
283 }
284 
runClangTidy(bool allFiles)285 void Plugin::runClangTidy(bool allFiles)
286 {
287     KDevelop::IDocument* doc = core()->documentController()->activeDocument();
288     if (!doc) {
289         QMessageBox::critical(nullptr, i18n("Error starting clang-tidy"),
290                               i18n("No suitable active file, unable to deduce project."));
291         return;
292     }
293 
294     KDevelop::IProject* project = core()->projectController()->findProjectForUrl(doc->url());
295     if (!project) {
296         QMessageBox::critical(nullptr, i18n("Error starting clang-tidy"), i18n("Active file isn't in a project"));
297         return;
298     }
299 
300     m_config = project->projectConfiguration()->group("ClangTidy");
301     if (!m_config.isValid()) {
302         QMessageBox::critical(nullptr, i18n("Error starting ClangTidy"),
303                               i18n("Can't load parameters. They must be set in the project settings."));
304         return;
305     }
306 
307     auto clangtidyPath = m_config.readEntry(ConfigGroup::ExecutablePath);
308     auto buildSystem = project->buildSystemManager();
309 
310     Job::Parameters params;
311 
312     params.projectRootDir = project->path().toLocalFile();
313 
314     // TODO: auto detect clang-tidy executable instead of hard-coding it.
315     if (clangtidyPath.isEmpty()) {
316         params.executablePath = QStringLiteral("/usr/bin/clang-tidy");
317     } else {
318         params.executablePath = clangtidyPath;
319     }
320 
321     if (allFiles) {
322         params.filePath = project->path().toUrl().toLocalFile();
323     } else {
324         params.filePath = doc->url().toLocalFile();
325     }
326     params.buildDir = buildSystem->buildDirectory(project->projectItem()).toLocalFile();
327     params.additionalParameters = m_config.readEntry(ConfigGroup::AdditionalParameters);
328     params.analiseTempDtors = m_config.readEntry(ConfigGroup::AnaliseTempDtors);
329     params.enabledChecks = m_activeChecks.join(',');
330     params.useConfigFile = m_config.readEntry(ConfigGroup::UseConfigFile);
331     params.dumpConfig = m_config.readEntry(ConfigGroup::DumpConfig);
332     params.enableChecksProfile = m_config.readEntry(ConfigGroup::EnableChecksProfile);
333     params.exportFixes = m_config.readEntry(ConfigGroup::ExportFixes);
334     params.extraArgs = m_config.readEntry(ConfigGroup::ExtraArgs);
335     params.extraArgsBefore = m_config.readEntry(ConfigGroup::ExtraArgsBefore);
336     params.autoFix = m_config.readEntry(ConfigGroup::AutoFix);
337     params.headerFilter = m_config.readEntry(ConfigGroup::HeaderFilter);
338     params.lineFilter = m_config.readEntry(ConfigGroup::LineFilter);
339     params.listChecks = m_config.readEntry(ConfigGroup::ListChecks);
340     params.checkSystemHeaders = m_config.readEntry(ConfigGroup::CheckSystemHeaders);
341 
342     if (!params.dumpConfig.isEmpty()) {
343         Job* job = new ClangTidy::Job(params, this);
344         core()->runController()->registerJob(job);
345         params.dumpConfig = QString();
346     }
347     Job* job2 = new ClangTidy::Job(params, this);
348     connect(job2, SIGNAL(finished(KJob*)), this, SLOT(result(KJob*)));
349     core()->runController()->registerJob(job2);
350 }
351 
runClangTidyFile()352 void Plugin::runClangTidyFile()
353 {
354     bool allFiles = false;
355     runClangTidy(allFiles);
356 }
357 
runClangTidyAll()358 void Plugin::runClangTidyAll()
359 {
360     bool allFiles = true;
361     runClangTidy(allFiles);
362 }
363 
loadOutput()364 void Plugin::loadOutput()
365 {
366 }
367 
result(KJob * job)368 void Plugin::result(KJob* job)
369 {
370     Job* aj = dynamic_cast<Job*>(job);
371     if (!aj) {
372         return;
373     }
374 
375     if (aj->status() == KDevelop::OutputExecuteJob::JobStatus::JobSucceeded) {
376         m_model->setProblems(aj->problems());
377 
378         core()->uiController()->findToolView(i18nd("kdevproblemreporter", "Problems"), 0,
379                                              KDevelop::IUiController::FindFlags::Raise);
380     }
381 }
382 
contextMenuExtension(KDevelop::Context * context)383 KDevelop::ContextMenuExtension Plugin::contextMenuExtension(KDevelop::Context* context)
384 {
385     KDevelop::IDocument* doc = core()->documentController()->activeDocument();
386     KDevelop::ContextMenuExtension extension = KDevelop::IPlugin::contextMenuExtension(context);
387 
388     if (context->type() == KDevelop::Context::EditorContext) {
389 
390         auto mime = doc->mimeType().name();
391         if (mime == QLatin1String("text/x-c++src") || mime == QLatin1String("text/x-csrc")) {
392             QAction* action
393                 = new QAction(QIcon::fromTheme("document-new"), i18n("Check current unit with clang-tidy"), this);
394             connect(action, SIGNAL(triggered(bool)), this, SLOT(runClangTidyFile()));
395             extension.addAction(KDevelop::ContextMenuExtension::AnalyzeGroup, action);
396         }
397     }
398     return extension;
399 }
400 
perProjectConfigPage(int number,const ProjectConfigOptions & options,QWidget * parent)401 KDevelop::ConfigPage* Plugin::perProjectConfigPage(int number, const ProjectConfigOptions& options, QWidget* parent)
402 {
403     if (number != 0) {
404         return nullptr;
405     } else {
406         auto config = new PerProjectConfigPage(options.project, parent);
407         config->setActiveChecksReceptorList(&m_activeChecks);
408         config->setList(m_allChecks);
409         return config;
410     }
411 }
412 
configPage(int number,QWidget * parent)413 KDevelop::ConfigPage* Plugin::configPage(int number, QWidget* parent)
414 {
415     if (number != 0) {
416         return nullptr;
417     } else {
418         return new ClangTidyPreferences(this, parent);
419     }
420 }
421 }
422 
423 #include "plugin.moc"
424