1 /****************************************************************************
2 **
3 ** Copyright (C) 2019 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qt Creator.
7 **
8 ** Commercial License Usage
9 ** Licensees holding valid commercial Qt licenses may use this file in
10 ** accordance with the commercial license agreement provided with the
11 ** Software or, alternatively, in accordance with the terms contained in
12 ** a written agreement between you and The Qt Company. For licensing terms
13 ** and conditions see https://www.qt.io/terms-conditions. For further
14 ** information use the contact form at https://www.qt.io/contact-us.
15 **
16 ** GNU General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU
18 ** General Public License version 3 as published by the Free Software
19 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
20 ** included in the packaging of this file. Please review the following
21 ** information to ensure the GNU General Public License requirements will
22 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
23 **
24 ****************************************************************************/
25 
26 #include "diagnosticconfigswidget.h"
27 
28 #include "clangtoolsdiagnostic.h"
29 #include "clangtoolsprojectsettings.h"
30 #include "clangtoolssettings.h"
31 #include "clangtoolsutils.h"
32 #include "executableinfo.h"
33 
34 #include "ui_clazychecks.h"
35 #include "ui_tidychecks.h"
36 
37 #include <cpptools/cppcodemodelsettings.h>
38 #include <cpptools/cpptoolsconstants.h>
39 #include <cpptools/cpptoolsreuse.h>
40 #include <projectexplorer/selectablefilesmodel.h>
41 #include <projectexplorer/session.h>
42 
43 #include <utils/algorithm.h>
44 #include <utils/fileutils.h>
45 #include <utils/qtcassert.h>
46 #include <utils/stringutils.h>
47 
48 #include <QDesktopServices>
49 #include <QDialog>
50 #include <QDialogButtonBox>
51 #include <QHBoxLayout>
52 #include <QPushButton>
53 #include <QSortFilterProxyModel>
54 #include <QStringListModel>
55 #include <QTreeWidget>
56 #include <QTreeWidgetItem>
57 #include <QUuid>
58 #include <QVBoxLayout>
59 
60 using namespace CppTools;
61 
62 namespace ClangTools {
63 namespace Internal {
64 
65 class TidyOptionsDialog : public QDialog
66 {
67     Q_OBJECT
68 public:
TidyOptionsDialog(const QString & check,const ClangDiagnosticConfig::TidyCheckOptions & options,QWidget * parent=nullptr)69     TidyOptionsDialog(const QString &check, const ClangDiagnosticConfig::TidyCheckOptions &options,
70                       QWidget *parent = nullptr) : QDialog(parent)
71     {
72         setWindowTitle(tr("Options for %1").arg(check));
73         resize(600, 300);
74         const auto mainLayout = new QVBoxLayout(this);
75         const auto widgetLayout = new QHBoxLayout;
76         mainLayout->addLayout(widgetLayout);
77         m_optionsWidget.setColumnCount(2);
78         m_optionsWidget.setHeaderLabels({tr("Option"), tr("Value")});
79         const auto addItem = [this](const QString &option, const QString &value) {
80             const auto item = new QTreeWidgetItem(&m_optionsWidget, QStringList{option, value});
81             item->setFlags(item->flags() | Qt::ItemIsEditable);
82             return item;
83         };
84         for (auto it = options.begin(); it != options.end(); ++it)
85             addItem(it.key(), it.value());
86         m_optionsWidget.resizeColumnToContents(0);
87         widgetLayout->addWidget(&m_optionsWidget);
88         const auto buttonLayout = new QVBoxLayout;
89         widgetLayout->addLayout(buttonLayout);
90         const auto addButton = new QPushButton(tr("Add Option"));
91         connect(addButton, &QPushButton::clicked, this, [this, addItem] {
92             const auto item = addItem(tr("<new option>"), {});
93             m_optionsWidget.editItem(item);
94         });
95         buttonLayout->addWidget(addButton);
96         const auto removeButton = new QPushButton(tr("Remove Option"));
97         connect(removeButton, &QPushButton::clicked, this, [this] {
98             qDeleteAll(m_optionsWidget.selectedItems());
99         });
100         const auto toggleRemoveButtonEnabled = [this, removeButton] {
101             removeButton->setEnabled(!m_optionsWidget.selectionModel()->selectedRows().isEmpty());
102         };
103         connect(&m_optionsWidget, &QTreeWidget::itemSelectionChanged,
104                 this, [toggleRemoveButtonEnabled] { toggleRemoveButtonEnabled(); });
105         toggleRemoveButtonEnabled();
106         buttonLayout->addWidget(removeButton);
107         buttonLayout->addStretch(1);
108 
109         const auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok
110                                                     | QDialogButtonBox::Cancel);
111         connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
112         connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
113         mainLayout->addWidget(buttonBox);
114     }
115 
options() const116     ClangDiagnosticConfig::TidyCheckOptions options() const
117     {
118         ClangDiagnosticConfig::TidyCheckOptions opts;
119         for (int i = 0; i < m_optionsWidget.topLevelItemCount(); ++i) {
120             const QTreeWidgetItem * const item = m_optionsWidget.topLevelItem(i);
121             opts.insert(item->text(0), item->text(1));
122         }
123         return opts;
124     }
125 
126 private:
127     QTreeWidget m_optionsWidget;
128 };
129 
130 namespace ClangTidyPrefixTree {
131 
132 class Node
133 {
134 public:
135     Node() = default;
Node(const QString & name,const QVector<Node> & children=QVector<Node> ())136     Node(const QString &name, const QVector<Node> &children = QVector<Node>())
137         : name(name)
138         , children(children)
139     {}
140 
141     static Node fromCheckList(const QStringList &checks);
142 
143     QString name;
144     QVector<Node> children;
145 };
146 
147 class PrefixGroupIterator
148 {
149 public:
150     // Assumes sorted checks.
PrefixGroupIterator(const QStringList & checks,const QChar & itemSeparator)151     PrefixGroupIterator(const QStringList &checks, const QChar &itemSeparator)
152         : m_checks(checks)
153         , m_itemSeparator(itemSeparator)
154     {}
155 
next()156     QStringList next()
157     {
158         m_groupPrefix.clear();
159 
160         int i = m_index;
161         for (; i < m_checks.size(); ++i) {
162             const QString item = m_checks[i];
163             const QString prefix = itemPrefix(item);
164 
165             if (m_groupPrefix.isEmpty()) {
166                 if (prefix.isEmpty()) {
167                     m_index = i + 1;
168                     return {item};
169                 } else {
170                     m_groupPrefix = prefix;
171                 }
172             } else {
173                 if (!prefix.isEmpty() && prefix == groupPrefix())
174                     continue;
175                 return groupUntil(i - 1);
176             }
177         }
178 
179         return groupUntil(i);
180     }
181 
groupPrefix() const182     QString groupPrefix() const { return m_groupPrefix; }
183 
184 private:
itemPrefix(const QString & item) const185     QString itemPrefix(const QString &item) const
186     {
187         const int separatorIndex = item.indexOf(m_itemSeparator);
188         if (separatorIndex != -1)
189             return item.mid(0, separatorIndex);
190         return {};
191     }
192 
groupUntil(int i)193     QStringList groupUntil(int i)
194     {
195         const QStringList result = m_checks.mid(m_index, i - m_index + 1);
196         m_index = i + 1;
197         return result;
198     }
199 
200     QStringList m_checks;
201     QString m_groupPrefix;
202     QChar m_itemSeparator;
203     int m_index = 0;
204 };
205 
groupWithPrefixRemoved(const QStringList & group,const QString & prefix)206 static QStringList groupWithPrefixRemoved(const QStringList &group, const QString &prefix)
207 {
208     return Utils::transform(group, [&](const QString &item) { return item.mid(prefix.size() + 1); });
209 }
210 
groupToNode(const QString & nodeName,const QString & fullNodeName,const QStringList & checks,int uncompressedLevels)211 static Node groupToNode(const QString &nodeName,
212                         const QString &fullNodeName,
213                         const QStringList &checks,
214                         int uncompressedLevels)
215 {
216     // The clang (static) analyzer items are further separated by '.' instead of '-'.
217     const QChar nodeNameSeparator = fullNodeName.startsWith("clang-analyzer-") ? '.' : '-';
218     const QChar itemSeparator = fullNodeName.startsWith("clang-analyzer") ? '.' : '-';
219 
220     Node node = nodeName;
221     if (!nodeName.isEmpty())
222         node.name += nodeNameSeparator;
223 
224     // Iterate through prefix groups and add child nodes recursively
225     PrefixGroupIterator it(checks, itemSeparator);
226     for (QStringList group = it.next(); !group.isEmpty(); group = it.next()) {
227         const QString groupPrefix = it.groupPrefix();
228         const QString newFullNodeName = fullNodeName.isEmpty()
229                                             ? groupPrefix
230                                             : fullNodeName + nodeNameSeparator + groupPrefix;
231         const Node childNode = groupPrefix.isEmpty()
232                                    ? Node(group.first(), {})
233                                    : groupToNode(groupPrefix,
234                                                  newFullNodeName,
235                                                  groupWithPrefixRemoved(group, groupPrefix),
236                                                  std::max(uncompressedLevels - 1, 0));
237         node.children << childNode;
238     }
239 
240     // Eliminate pointless chains of single items
241     while (!uncompressedLevels && node.children.size() == 1) {
242         node.name = node.name + node.children[0].name;
243         node.children = node.children[0].children;
244     }
245 
246     return node;
247 }
248 
fromCheckList(const QStringList & allChecks)249 Node Node::fromCheckList(const QStringList &allChecks)
250 {
251     QStringList sortedChecks = allChecks;
252     sortedChecks.sort();
253 
254     return groupToNode("", "", sortedChecks, 2);
255 }
256 
257 } // namespace ClangTidyPrefixTree
258 
buildTree(ProjectExplorer::Tree * parent,ProjectExplorer::Tree * current,const ClangTidyPrefixTree::Node & node)259 static void buildTree(ProjectExplorer::Tree *parent,
260                       ProjectExplorer::Tree *current,
261                       const ClangTidyPrefixTree::Node &node)
262 {
263     current->name = node.name;
264     current->isDir = node.children.size();
265     if (parent) {
266         current->fullPath = parent->fullPath + current->name;
267         parent->childDirectories.push_back(current);
268     } else {
269         current->fullPath = Utils::FilePath::fromString(current->name);
270     }
271     current->parent = parent;
272     for (const ClangTidyPrefixTree::Node &nodeChild : node.children)
273         buildTree(current, new ProjectExplorer::Tree, nodeChild);
274 }
275 
needsLink(ProjectExplorer::Tree * node)276 static bool needsLink(ProjectExplorer::Tree *node) {
277     if (node->fullPath.toString() == "clang-analyzer-")
278         return true;
279     return !node->isDir && !node->fullPath.toString().startsWith("clang-analyzer-");
280 }
281 
282 class BaseChecksTreeModel : public ProjectExplorer::SelectableFilesModel
283 {
284     Q_OBJECT
285 
286 public:
287     enum Roles { LinkRole = Qt::UserRole + 1 };
288     enum Columns { NameColumn, LinkColumn };
289 
BaseChecksTreeModel()290     BaseChecksTreeModel()
291         : ProjectExplorer::SelectableFilesModel(nullptr)
292     {}
293 
columnCount(const QModelIndex &) const294     int columnCount(const QModelIndex &) const override { return 2; }
295 
data(const QModelIndex & fullIndex,int role=Qt::DisplayRole) const296     QVariant data(const QModelIndex &fullIndex, int role = Qt::DisplayRole) const override
297     {
298         if (fullIndex.column() == LinkColumn) {
299             switch (role) {
300             case Qt::DisplayRole:
301                 return tr("Web Page");
302             case Qt::FontRole: {
303                 QFont font = QApplication::font();
304                 font.setUnderline(true);
305                 return font;
306             }
307             case Qt::ForegroundRole:
308                 return QApplication::palette().link().color();
309             }
310             return QVariant();
311         }
312         return QVariant();
313     }
314 
setData(const QModelIndex & index,const QVariant & value,int role=Qt::EditRole)315     bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override
316     {
317         if (role == Qt::CheckStateRole && !m_enabled)
318             return false;
319         ProjectExplorer::SelectableFilesModel::setData(index, value, role);
320         return true;
321     }
322 
setEnabled(bool enabled)323     void setEnabled(bool enabled)
324     {
325         m_enabled = enabled;
326     }
327 
328     // TODO: Remove/replace this method after base class refactoring is done.
traverse(const QModelIndex & index,const std::function<bool (const QModelIndex &)> & visit) const329     void traverse(const QModelIndex &index,
330                   const std::function<bool(const QModelIndex &)> &visit) const
331     {
332         if (!index.isValid())
333             return;
334 
335         if (!visit(index))
336             return;
337 
338         if (!hasChildren(index))
339             return;
340 
341         const int rows = rowCount(index);
342         const int cols = columnCount(index);
343         for (int i = 0; i < rows; ++i) {
344             for (int j = 0; j < cols; ++j)
345                 traverse(this->index(i, j, index), visit);
346         }
347     }
348 
indexForName(const QString & name) const349     QModelIndex indexForName(const QString &name) const
350     {
351         return indexForName(QModelIndex(), name);
352     }
353 
354 protected:
355     bool m_enabled = true;
356 
357 private:
indexForName(const QModelIndex & current,const QString & name) const358     QModelIndex indexForName(const QModelIndex &current, const QString &name) const
359     {
360         QString nodeName;
361         QString remainingName = name;
362         if (current.isValid()) {
363             nodeName = data(current).toString();
364             if (nodeName == name)
365                 return current;
366             if (nodeName.endsWith('*'))
367                 nodeName.chop(1);
368             if (!name.startsWith(nodeName)) {
369                 if (!nodeName.contains("Level"))
370                     return {};
371             } else {
372                 remainingName = name.mid(nodeName.length());
373             }
374         }
375         const int childCount = rowCount(current);
376         for (int i = 0; i < childCount; ++i) {
377             const QModelIndex theIndex = indexForName(index(i, 0, current), remainingName);
378             if (theIndex.isValid())
379                 return theIndex;
380         }
381         return {};
382     }
383 };
384 
openUrl(QAbstractItemModel * model,const QModelIndex & index)385 static void openUrl(QAbstractItemModel *model, const QModelIndex &index)
386 {
387     const QString link = model->data(index, BaseChecksTreeModel::LinkRole).toString();
388     if (link.isEmpty())
389         return;
390 
391     QDesktopServices::openUrl(QUrl(link));
392 };
393 
394 class TidyChecksTreeModel final : public BaseChecksTreeModel
395 {
396     Q_OBJECT
397 
398 public:
TidyChecksTreeModel(const QStringList & supportedChecks)399     TidyChecksTreeModel(const QStringList &supportedChecks)
400     {
401         buildTree(nullptr, m_root, ClangTidyPrefixTree::Node::fromCheckList(supportedChecks));
402     }
403 
selectedChecks() const404     QString selectedChecks() const
405     {
406         QString checks;
407         collectChecks(m_root, checks);
408         return "-*" + checks;
409     }
410 
selectChecks(const QString & checks)411     void selectChecks(const QString &checks)
412     {
413         m_root->checked = Qt::Unchecked;
414         propagateDown(index(0, 0, QModelIndex()));
415 
416         QStringList checksList = checks.simplified().remove(" ")
417                 .split(",", Qt::SkipEmptyParts);
418 
419         for (QString &check : checksList) {
420             Qt::CheckState state;
421             if (check.startsWith("-")) {
422                 check = check.right(check.length() - 1);
423                 state = Qt::Unchecked;
424             } else {
425                 state = Qt::Checked;
426             }
427             const QModelIndex index = indexForCheck(check);
428             if (!index.isValid())
429                 continue;
430             auto *node = static_cast<ProjectExplorer::Tree *>(index.internalPointer());
431             node->checked = state;
432             propagateUp(index);
433             propagateDown(index);
434         }
435     }
436 
data(const QModelIndex & fullIndex,int role=Qt::DisplayRole) const437     QVariant data(const QModelIndex &fullIndex, int role = Qt::DisplayRole) const final
438     {
439         if (!fullIndex.isValid() || role == Qt::DecorationRole)
440             return QVariant();
441         QModelIndex index = this->index(fullIndex.row(), 0, fullIndex.parent());
442         auto *node = static_cast<ProjectExplorer::Tree *>(index.internalPointer());
443 
444         if (fullIndex.column() == 1) {
445             if (!needsLink(node))
446                 return QVariant();
447 
448             if (role == LinkRole || role == Qt::ToolTipRole) {
449                 // 'clang-analyzer-' group
450                 if (node->isDir)
451                     return CppTools::Constants::CLANG_STATIC_ANALYZER_DOCUMENTATION_URL;
452                 return clangTidyDocUrl(node->fullPath.toString());
453             }
454 
455             return BaseChecksTreeModel::data(fullIndex, role);
456         }
457 
458         if (fullIndex.column() == 2) {
459             if (hasChildren(fullIndex))
460                 return {};
461             if (role == Qt::DisplayRole)
462                 return tr("Options");
463             if (role == Qt::FontRole || role == Qt::ForegroundRole) {
464                 return BaseChecksTreeModel::data(fullIndex.sibling(fullIndex.row(), LinkColumn),
465                                                  role);
466             }
467             return {};
468         }
469 
470         if (role == Qt::DisplayRole)
471             return node->isDir ? (node->name + "*") : node->name;
472 
473         return ProjectExplorer::SelectableFilesModel::data(index, role);
474     }
475 
476 private:
columnCount(const QModelIndex &) const477     int columnCount(const QModelIndex &) const final { return 3; }
478 
indexForCheck(const QString & check) const479     QModelIndex indexForCheck(const QString &check) const {
480         if (check == "*")
481             return index(0, 0, QModelIndex());
482 
483         QModelIndex result;
484         traverse(index(0, 0, QModelIndex()), [&](const QModelIndex &index){
485             using ProjectExplorer::Tree;
486             if (result.isValid())
487                 return false;
488 
489             auto *node = static_cast<Tree *>(index.internalPointer());
490             const QString nodeName = node->fullPath.toString();
491             if ((check.endsWith("*") && nodeName.startsWith(check.left(check.length() - 1)))
492                     || (!node->isDir && nodeName == check)) {
493                 result = index;
494                 return false;
495             }
496 
497             return check.startsWith(nodeName);
498         });
499         return result;
500     }
501 
collectChecks(const ProjectExplorer::Tree * root,QString & checks)502     static void collectChecks(const ProjectExplorer::Tree *root, QString &checks)
503     {
504         if (root->checked == Qt::Unchecked)
505             return;
506         if (root->checked == Qt::Checked) {
507             checks += "," + root->fullPath.toString();
508             if (root->isDir)
509                 checks += "*";
510             return;
511         }
512         for (const ProjectExplorer::Tree *t : root->childDirectories)
513             collectChecks(t, checks);
514     }
515 };
516 
517 class ClazyChecksTree : public ProjectExplorer::Tree
518 {
519 public:
520     enum Kind { TopLevelNode, LevelNode, CheckNode };
ClazyChecksTree(const QString & name,Kind kind)521     ClazyChecksTree(const QString &name, Kind kind)
522     {
523         this->name = name;
524         this->kind = kind;
525         this->isDir = kind == TopLevelNode || kind == LevelNode;
526     }
527 
fromIndex(const QModelIndex & index)528     static ClazyChecksTree *fromIndex(const QModelIndex &index)
529     {
530         return static_cast<ClazyChecksTree *>(index.internalPointer());
531     }
532 
533 public:
534     ClazyCheck check;
535     Kind kind = TopLevelNode;
536 };
537 
538 class ClazyChecksTreeModel final : public BaseChecksTreeModel
539 {
540     Q_OBJECT
541 
542 public:
ClazyChecksTreeModel(const ClazyChecks & supportedClazyChecks)543     ClazyChecksTreeModel(const ClazyChecks &supportedClazyChecks)
544     {
545         // Top level node
546         m_root = new ClazyChecksTree("*", ClazyChecksTree::TopLevelNode);
547 
548         for (const ClazyCheck &check : supportedClazyChecks) {
549             // Level node
550             ClazyChecksTree *&levelNode = m_levelNodes[check.level];
551             if (!levelNode) {
552                 levelNode = new ClazyChecksTree(levelDescription(check.level),
553                                                 ClazyChecksTree::LevelNode);
554                 levelNode->parent = m_root;
555                 levelNode->check.level = check.level; // Pass on the level for sorting
556                 m_root->childDirectories << levelNode;
557             }
558 
559             // Check node
560             auto checkNode = new ClazyChecksTree(check.name, ClazyChecksTree::CheckNode);
561             checkNode->parent = levelNode;
562             checkNode->check = check;
563 
564             levelNode->childDirectories.append(checkNode);
565 
566             m_topics.unite(Utils::toSet(check.topics));
567         }
568     }
569 
enabledChecks() const570     QStringList enabledChecks() const
571     {
572         QStringList checks;
573         collectChecks(m_root, checks);
574         return checks;
575     }
576 
enableChecks(const QStringList & checks)577     void enableChecks(const QStringList &checks)
578     {
579         // Unselect all
580         m_root->checked = Qt::Unchecked;
581         propagateDown(index(0, 0, QModelIndex()));
582 
583         for (const QString &check : checks) {
584             const QModelIndex index = indexForCheck(check);
585             if (!index.isValid())
586                 continue;
587             ClazyChecksTree::fromIndex(index)->checked = Qt::Checked;
588             propagateUp(index);
589             propagateDown(index);
590         }
591     }
592 
hasEnabledButNotVisibleChecks(const std::function<bool (const QModelIndex & index)> & isHidden) const593     bool hasEnabledButNotVisibleChecks(
594         const std::function<bool(const QModelIndex &index)> &isHidden) const
595     {
596         bool enabled = false;
597         traverse(index(0, 0, QModelIndex()), [&](const QModelIndex &index){
598             if (enabled)
599                 return false;
600             const auto *node = ClazyChecksTree::fromIndex(index);
601             if (node->kind == ClazyChecksTree::CheckNode && index.column() == NameColumn) {
602                 const bool isChecked = data(index, Qt::CheckStateRole).toInt() == Qt::Checked;
603                 const bool isVisible = isHidden(index);
604                 if (isChecked && isVisible) {
605                     enabled = true;
606                     return false;
607                 }
608             }
609             return true;
610         });
611 
612         return enabled;
613     }
614 
enableLowerLevels() const615     bool enableLowerLevels() const { return m_enableLowerLevels; }
setEnableLowerLevels(bool enable)616     void setEnableLowerLevels(bool enable) { m_enableLowerLevels = enable; }
617 
topics() const618     QSet<QString> topics() const { return m_topics; }
619 
620 private:
data(const QModelIndex & fullIndex,int role=Qt::DisplayRole) const621     QVariant data(const QModelIndex &fullIndex, int role = Qt::DisplayRole) const final
622     {
623         if (!fullIndex.isValid() || role == Qt::DecorationRole)
624             return QVariant();
625         const QModelIndex index = this->index(fullIndex.row(), 0, fullIndex.parent());
626         const auto *node = ClazyChecksTree::fromIndex(index);
627 
628         if (fullIndex.column() == LinkColumn) {
629             if (role == LinkRole || role == Qt::ToolTipRole) {
630                 if (node->check.name.isEmpty())
631                     return QVariant();
632                 return clazyDocUrl(node->name);
633             }
634             if (role == Qt::DisplayRole && node->kind != ClazyChecksTree::CheckNode)
635                 return QVariant();
636 
637             return BaseChecksTreeModel::data(fullIndex, role);
638         }
639 
640         if (role == Qt::DisplayRole)
641             return node->name;
642 
643         return ProjectExplorer::SelectableFilesModel::data(index, role);
644     }
645 
levelDescription(int level)646     static QString levelDescription(int level)
647     {
648         switch (level) {
649         case -1:
650             return tr("Manual Level: Very few false positives");
651         case 0:
652             return tr("Level 0: No false positives");
653         case 1:
654             return tr("Level 1: Very few false positives");
655         case 2:
656             return tr("Level 2: More false positives");
657         case 3:
658             return tr("Level 3: Experimental checks");
659         default:
660             QTC_CHECK(false && "No clazy level description");
661             return tr("Level %1").arg(QString::number(level));
662         }
663     }
664 
indexForCheck(const QString & check) const665     QModelIndex indexForCheck(const QString &check) const {
666         if (check == "*")
667             return index(0, 0, QModelIndex());
668 
669         QModelIndex result;
670         traverse(index(0, 0, QModelIndex()), [&](const QModelIndex &index){
671             if (result.isValid())
672                 return false;
673             const auto *node = ClazyChecksTree::fromIndex(index);
674             if (node->kind == ClazyChecksTree::CheckNode && node->check.name == check) {
675                 result = index;
676                 return false;
677             }
678             return true;
679         });
680         return result;
681     }
682 
indexForTree(const ClazyChecksTree * tree) const683     QModelIndex indexForTree(const ClazyChecksTree *tree) const {
684         if (!tree)
685             return {};
686 
687         QModelIndex result;
688         traverse(index(0, 0, QModelIndex()), [&](const QModelIndex &index){
689             if (result.isValid())
690                 return false;
691             if (index.internalPointer() == tree) {
692                 result = index;
693                 return false;
694             }
695             return true;
696         });
697         return result;
698     }
699 
collectChecks(const ProjectExplorer::Tree * root,QStringList & checks)700     static void collectChecks(const ProjectExplorer::Tree *root, QStringList &checks)
701     {
702         if (root->checked == Qt::Unchecked)
703             return;
704         if (root->checked == Qt::Checked && !root->isDir) {
705             checks.append(root->name);
706             return;
707         }
708         for (const ProjectExplorer::Tree *t : root->childDirectories)
709             collectChecks(t, checks);
710     }
711 
toStringList(const QVariantList & variantList)712     static QStringList toStringList(const QVariantList &variantList)
713     {
714         QStringList list;
715         for (auto &item : variantList)
716             list.append(item.toString());
717         return list;
718     }
719 
720 private:
721     QHash<int, ClazyChecksTree *> m_levelNodes;
722     QSet<QString> m_topics;
723     bool m_enableLowerLevels = true;
724 };
725 
726 class ClazyChecksSortFilterModel : public QSortFilterProxyModel
727 {
728 public:
ClazyChecksSortFilterModel(QObject * parent)729     ClazyChecksSortFilterModel(QObject *parent)
730         : QSortFilterProxyModel(parent)
731     {}
732 
setTopics(const QStringList & value)733     void setTopics(const QStringList &value)
734     {
735         m_topics = value;
736         invalidateFilter();
737     }
738 
filterAcceptsRow(int sourceRow,const QModelIndex & sourceParent) const739     bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override
740     {
741         if (!QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent))
742             return false;
743 
744         const QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
745         if (!index.isValid())
746             return false;
747 
748         const auto *node = ClazyChecksTree::fromIndex(index);
749         if (node->kind == ClazyChecksTree::CheckNode) {
750             const QStringList topics = node->check.topics;
751             return m_topics.isEmpty() || Utils::anyOf(m_topics, [topics](const QString &topic) {
752                 return topics.contains(topic);
753             });
754         }
755 
756         return true;
757     }
758 
759 private:
760     // Note that sort order of levels is important for "enableLowerLevels" mode, see setData().
lessThan(const QModelIndex & l,const QModelIndex & r) const761     bool lessThan(const QModelIndex &l, const QModelIndex &r) const override
762     {
763         const int leftLevel = adaptLevel(ClazyChecksTree::fromIndex(l)->check.level);
764         const int rightLevel = adaptLevel(ClazyChecksTree::fromIndex(r)->check.level);
765 
766         if (leftLevel == rightLevel)
767             return sourceModel()->data(l).toString() < sourceModel()->data(r).toString();
768         return leftLevel < rightLevel;
769     }
770 
adaptLevel(int level)771     static int adaptLevel(int level)
772     {
773         if (level == -1) // "Manual Level"
774             return 1000;
775         return level;
776     }
777 
setData(const QModelIndex & index,const QVariant & value,int role=Qt::EditRole)778     bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override
779     {
780         if (!index.isValid())
781             return false;
782 
783         if (role == Qt::CheckStateRole
784               && static_cast<ClazyChecksTreeModel *>(sourceModel())->enableLowerLevels()
785               && QSortFilterProxyModel::setData(index, value, role)) {
786             const auto *node = ClazyChecksTree::fromIndex(mapToSource(index));
787             if (node->kind == ClazyChecksTree::LevelNode && node->check.level >= 0) {
788                 // Rely on the sort order to find the lower level index/node
789                 const auto previousIndex = this->index(index.row() - 1,
790                                                        index.column(),
791                                                        index.parent());
792                 if (previousIndex.isValid()
793                     && ClazyChecksTree::fromIndex(mapToSource(previousIndex))->check.level
794                            >= 0) {
795                     setData(previousIndex, value, role);
796                 }
797             }
798         }
799 
800         return QSortFilterProxyModel::setData(index, value, role);
801     }
802 
803 private:
804     QStringList m_topics;
805 };
806 
setupTreeView(QTreeView * view,QAbstractItemModel * model,int expandToDepth=0)807 static void setupTreeView(QTreeView *view, QAbstractItemModel *model, int expandToDepth = 0)
808 {
809     view->setModel(model);
810     view->expandToDepth(expandToDepth);
811     view->header()->setStretchLastSection(false);
812     view->header()->setSectionResizeMode(0, QHeaderView::Stretch);
813     view->setHeaderHidden(true);
814 }
815 
DiagnosticConfigsWidget(const ClangDiagnosticConfigs & configs,const Utils::Id & configToSelect,const ClangTidyInfo & tidyInfo,const ClazyStandaloneInfo & clazyInfo)816 DiagnosticConfigsWidget::DiagnosticConfigsWidget(const ClangDiagnosticConfigs &configs,
817                                                  const Utils::Id &configToSelect,
818                                                  const ClangTidyInfo &tidyInfo,
819                                                  const ClazyStandaloneInfo &clazyInfo)
820     : ClangDiagnosticConfigsWidget(configs, configToSelect)
821     , m_tidyTreeModel(new TidyChecksTreeModel(tidyInfo.supportedChecks))
822     , m_tidyInfo(tidyInfo)
823     , m_clazyTreeModel(new ClazyChecksTreeModel(clazyInfo.supportedChecks))
824     , m_clazyInfo(clazyInfo)
825 {
826     m_clazyChecks = std::make_unique<Ui::ClazyChecks>();
827     m_clazyChecksWidget = new QWidget();
828     m_clazyChecks->setupUi(m_clazyChecksWidget);
829     m_clazyChecks->invalidExecutableLabel->setType(Utils::InfoLabel::Warning);
830     m_clazyChecks->invalidExecutableLabel->setElideMode(Qt::ElideNone);
831     m_clazySortFilterProxyModel = new ClazyChecksSortFilterModel(this);
832     m_clazySortFilterProxyModel->setSourceModel(m_clazyTreeModel.get());
833     m_clazySortFilterProxyModel->setRecursiveFilteringEnabled(true);
834 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
835     m_clazySortFilterProxyModel->setAutoAcceptChildRows(true);
836 #endif
837     setupTreeView(m_clazyChecks->checksView, m_clazySortFilterProxyModel, 2);
838     m_clazyChecks->filterLineEdit->setFiltering(true);
839     m_clazyChecks->filterLineEdit->setPlaceholderText(tr("Textual Filter"));
840     connect(m_clazyChecks->filterLineEdit, &Utils::FancyLineEdit::filterChanged,
841             m_clazySortFilterProxyModel,
842             qOverload<const QString &>(&QSortFilterProxyModel::setFilterRegularExpression));
843     m_clazyChecks->checksView->setSortingEnabled(true);
844     m_clazyChecks->checksView->sortByColumn(0, Qt::AscendingOrder);
845     auto topicsModel = new QStringListModel(Utils::toList(m_clazyTreeModel->topics()), this);
846     topicsModel->sort(0);
847     m_clazyChecks->topicsView->setModel(topicsModel);
848     connect(m_clazyChecks->topicsResetButton, &QPushButton::clicked, [this](){
849         m_clazyChecks->topicsView->clearSelection();
850     });
851     m_clazyChecks->topicsView->setSelectionMode(QAbstractItemView::MultiSelection);
852     connect(m_clazyChecks->topicsView->selectionModel(),
853             &QItemSelectionModel::selectionChanged,
854             [this, topicsModel](const QItemSelection &, const QItemSelection &) {
855                 const auto indexes = m_clazyChecks->topicsView->selectionModel()->selectedIndexes();
856                 const QStringList topics
857                     = Utils::transform(indexes, [topicsModel](const QModelIndex &index) {
858                           return topicsModel->data(index).toString();
859                       });
860                 m_clazySortFilterProxyModel->setTopics(topics);
861                 this->syncClazyChecksGroupBox();
862             });
863 
864     connect(m_clazyChecks->checksView,
865             &QTreeView::clicked,
866             [model = m_clazySortFilterProxyModel](const QModelIndex &index) {
867                 openUrl(model, index);
868             });
869     connect(m_clazyChecks->enableLowerLevelsCheckBox, &QCheckBox::stateChanged, [this](int) {
870         const bool enable = m_clazyChecks->enableLowerLevelsCheckBox->isChecked();
871         m_clazyTreeModel->setEnableLowerLevels(enable);
872         codeModelSettings()->setEnableLowerClazyLevels(
873             m_clazyChecks->enableLowerLevelsCheckBox->isChecked());
874     });
875     const Qt::CheckState checkEnableLowerClazyLevels
876         = codeModelSettings()->enableLowerClazyLevels() ? Qt::Checked : Qt::Unchecked;
877     m_clazyChecks->enableLowerLevelsCheckBox->setCheckState(checkEnableLowerClazyLevels);
878 
879     m_tidyChecks = std::make_unique<Ui::TidyChecks>();
880     m_tidyChecksWidget = new QWidget();
881     m_tidyChecks->setupUi(m_tidyChecksWidget);
882     m_tidyChecks->invalidExecutableLabel->setType(Utils::InfoLabel::Warning);
883     m_tidyChecks->invalidExecutableLabel->setElideMode(Qt::ElideNone);
884     const auto tidyFilterModel = new QSortFilterProxyModel(this);
885     tidyFilterModel->setRecursiveFilteringEnabled(true);
886 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
887     tidyFilterModel->setAutoAcceptChildRows(true);
888 #endif
889     tidyFilterModel->setSourceModel(m_tidyTreeModel.get());
890     setupTreeView(m_tidyChecks->checksPrefixesTree, tidyFilterModel);
891     m_tidyChecks->filterLineEdit->setFiltering(true);
892     connect(m_tidyChecks->filterLineEdit, &Utils::FancyLineEdit::filterChanged, tidyFilterModel,
893             qOverload<const QString &>(&QSortFilterProxyModel::setFilterRegularExpression));
894 
895     connect(m_tidyChecks->checksPrefixesTree,
896             &QTreeView::clicked,
897             [this, tidyFilterModel](const QModelIndex &proxyIndex) {
898                 const QModelIndex index = tidyFilterModel->mapToSource(proxyIndex);
899                 if (index.column() == 2) {
900                     if (m_tidyTreeModel->hasChildren(index))
901                         return;
902                     ClangDiagnosticConfig config = currentConfig();
903                     QString check;
904                     for (QModelIndex idx = index.siblingAtColumn(0); idx.isValid();
905                          idx = idx.parent()) {
906                         QString current = m_tidyTreeModel->data(idx).toString();
907                         if (current.endsWith('*'))
908                             current.chop(1);
909                         check.prepend(current);
910                     }
911                     TidyOptionsDialog dlg(check, config.tidyCheckOptions(check),
912                                           m_tidyChecks->checksPrefixesTree);
913                     if (dlg.exec() != QDialog::Accepted)
914                         return;
915                     config.setTidyCheckOptions(check, dlg.options());
916                     updateConfig(config);
917                     return;
918                 }
919                 openUrl(m_tidyTreeModel.get(), index);
920     });
921 
922     connect(m_tidyChecks->plainTextEditButton, &QPushButton::clicked, this, [this]() {
923         const bool readOnly = currentConfig().isReadOnly();
924 
925         QDialog dialog;
926         dialog.setWindowTitle(tr("Checks"));
927         dialog.setLayout(new QVBoxLayout);
928         auto *textEdit = new QTextEdit(&dialog);
929         textEdit->setReadOnly(readOnly);
930         dialog.layout()->addWidget(textEdit);
931         auto *buttonsBox = new QDialogButtonBox(QDialogButtonBox::Ok
932                                                 | (readOnly ? QDialogButtonBox::NoButton
933                                                             : QDialogButtonBox::Cancel));
934         dialog.layout()->addWidget(buttonsBox);
935         QObject::connect(buttonsBox, &QDialogButtonBox::accepted, &dialog, &QDialog::accept);
936         QObject::connect(buttonsBox, &QDialogButtonBox::rejected, &dialog, &QDialog::reject);
937         const QString initialChecks = m_tidyTreeModel->selectedChecks();
938         textEdit->setPlainText(initialChecks);
939 
940         QObject::connect(&dialog, &QDialog::accepted, [=, &initialChecks]() {
941             const QString updatedChecks = textEdit->toPlainText();
942             if (updatedChecks == initialChecks)
943                 return;
944 
945             disconnectClangTidyItemChanged();
946 
947             // Also throws away invalid options.
948             m_tidyTreeModel->selectChecks(updatedChecks);
949             onClangTidyTreeChanged();
950 
951             connectClangTidyItemChanged();
952         });
953         dialog.exec();
954     });
955 
956     connectClangTidyItemChanged();
957     connectClazyItemChanged();
958 
959     tabWidget()->addTab(m_tidyChecksWidget, tr("Clang-Tidy Checks"));
960     tabWidget()->addTab(m_clazyChecksWidget, tr("Clazy Checks"));
961 }
962 
963 DiagnosticConfigsWidget::~DiagnosticConfigsWidget() = default;
964 
syncClangTidyWidgets(const ClangDiagnosticConfig & config)965 void DiagnosticConfigsWidget::syncClangTidyWidgets(const ClangDiagnosticConfig &config)
966 {
967     enum TidyPages { // In sync with m_tidyChecks->stackedWidget pages.
968         ChecksPage,
969         EmptyPage,
970         InvalidExecutablePage,
971     };
972 
973     disconnectClangTidyItemChanged();
974 
975     const ClangDiagnosticConfig::TidyMode tidyMode = config.clangTidyMode();
976     switch (tidyMode) {
977     case ClangDiagnosticConfig::TidyMode::UseConfigFile:
978         m_tidyChecks->tidyMode->setCurrentIndex(1);
979         m_tidyChecks->plainTextEditButton->setVisible(false);
980         m_tidyChecks->stackedWidget->setCurrentIndex(TidyPages::EmptyPage);
981         break;
982     case ClangDiagnosticConfig::TidyMode::UseCustomChecks:
983     case ClangDiagnosticConfig::TidyMode::UseDefaultChecks:
984         m_tidyChecks->tidyMode->setCurrentIndex(0);
985         if (m_tidyInfo.supportedChecks.isEmpty()) {
986             m_tidyChecks->plainTextEditButton->setVisible(false);
987             m_tidyChecks->stackedWidget->setCurrentIndex(TidyPages::InvalidExecutablePage);
988         } else {
989             m_tidyChecks->plainTextEditButton->setVisible(true);
990             m_tidyChecks->stackedWidget->setCurrentIndex(TidyPages::ChecksPage);
991             syncTidyChecksToTree(config);
992         }
993         break;
994     }
995 
996     const bool enabled = !config.isReadOnly();
997     m_tidyChecks->tidyMode->setEnabled(enabled);
998     m_tidyChecks->plainTextEditButton->setText(enabled ? tr("Edit Checks as String...")
999                                                        : tr("View Checks as String..."));
1000     m_tidyTreeModel->setEnabled(enabled);
1001     connectClangTidyItemChanged();
1002 }
1003 
syncClazyWidgets(const ClangDiagnosticConfig & config)1004 void DiagnosticConfigsWidget::syncClazyWidgets(const ClangDiagnosticConfig &config)
1005 {
1006     enum ClazyPages { // In sync with m_clazyChecks->stackedWidget pages.
1007         ChecksPage,
1008         InvalidExecutablePage,
1009     };
1010 
1011     if (m_clazyInfo.supportedChecks.isEmpty()) {
1012         m_clazyChecks->stackedWidget->setCurrentIndex(ClazyPages::InvalidExecutablePage);
1013         return;
1014     }
1015 
1016     m_clazyChecks->stackedWidget->setCurrentIndex(ClazyPages::ChecksPage);
1017 
1018     disconnectClazyItemChanged();
1019     const QStringList checkNames = config.clazyMode()
1020                                            == ClangDiagnosticConfig::ClazyMode::UseDefaultChecks
1021                                        ? m_clazyInfo.defaultChecks
1022                                        : config.clazyChecks().split(',', Qt::SkipEmptyParts);
1023     m_clazyTreeModel->enableChecks(checkNames);
1024 
1025     syncClazyChecksGroupBox();
1026 
1027     const bool enabled = !config.isReadOnly();
1028     m_clazyChecks->topicsResetButton->setEnabled(enabled);
1029     m_clazyChecks->enableLowerLevelsCheckBox->setEnabled(enabled);
1030     m_clazyChecks->topicsView->clearSelection();
1031     m_clazyChecks->topicsView->setEnabled(enabled);
1032     m_clazyTreeModel->setEnabled(enabled);
1033 
1034     connectClazyItemChanged();
1035 }
1036 
syncTidyChecksToTree(const ClangDiagnosticConfig & config)1037 void DiagnosticConfigsWidget::syncTidyChecksToTree(const ClangDiagnosticConfig &config)
1038 {
1039     const QString checks = config.clangTidyMode()
1040                                    == ClangDiagnosticConfig::TidyMode::UseDefaultChecks
1041                                ? m_tidyInfo.defaultChecks.join(',')
1042                                : config.clangTidyChecks();
1043     m_tidyTreeModel->selectChecks(checks);
1044 }
1045 
syncExtraWidgets(const ClangDiagnosticConfig & config)1046 void DiagnosticConfigsWidget::syncExtraWidgets(const ClangDiagnosticConfig &config)
1047 {
1048     syncClangTidyWidgets(config);
1049     syncClazyWidgets(config);
1050 }
1051 
connectClangTidyItemChanged()1052 void DiagnosticConfigsWidget::connectClangTidyItemChanged()
1053 {
1054     connect(m_tidyChecks->tidyMode,
1055             QOverload<int>::of(&QComboBox::currentIndexChanged),
1056             this,
1057             &DiagnosticConfigsWidget::onClangTidyModeChanged);
1058     connect(m_tidyTreeModel.get(), &TidyChecksTreeModel::dataChanged,
1059             this, &DiagnosticConfigsWidget::onClangTidyTreeChanged);
1060 }
1061 
disconnectClangTidyItemChanged()1062 void DiagnosticConfigsWidget::disconnectClangTidyItemChanged()
1063 {
1064     disconnect(m_tidyChecks->tidyMode,
1065                QOverload<int>::of(&QComboBox::currentIndexChanged),
1066                this,
1067                &DiagnosticConfigsWidget::onClangTidyModeChanged);
1068     disconnect(m_tidyTreeModel.get(), &TidyChecksTreeModel::dataChanged,
1069                this, &DiagnosticConfigsWidget::onClangTidyTreeChanged);
1070 }
1071 
connectClazyItemChanged()1072 void DiagnosticConfigsWidget::connectClazyItemChanged()
1073 {
1074     connect(m_clazyTreeModel.get(), &ClazyChecksTreeModel::dataChanged,
1075             this, &DiagnosticConfigsWidget::onClazyTreeChanged);
1076 }
1077 
disconnectClazyItemChanged()1078 void DiagnosticConfigsWidget::disconnectClazyItemChanged()
1079 {
1080     disconnect(m_clazyTreeModel.get(), &ClazyChecksTreeModel::dataChanged,
1081                this, &DiagnosticConfigsWidget::onClazyTreeChanged);
1082 }
1083 
onClangTidyModeChanged(int index)1084 void DiagnosticConfigsWidget::onClangTidyModeChanged(int index)
1085 {
1086     const ClangDiagnosticConfig::TidyMode tidyMode
1087         = index == 0 ? ClangDiagnosticConfig::TidyMode::UseCustomChecks
1088                      : ClangDiagnosticConfig::TidyMode::UseConfigFile;
1089 
1090     ClangDiagnosticConfig config = currentConfig();
1091     config.setClangTidyMode(tidyMode);
1092     updateConfig(config);
1093     syncClangTidyWidgets(config);
1094 }
1095 
onClangTidyTreeChanged()1096 void DiagnosticConfigsWidget::onClangTidyTreeChanged()
1097 {
1098     ClangDiagnosticConfig config = currentConfig();
1099     if (config.clangTidyMode() == ClangDiagnosticConfig::TidyMode::UseDefaultChecks)
1100         config.setClangTidyMode(ClangDiagnosticConfig::TidyMode::UseCustomChecks);
1101     config.setClangTidyChecks(m_tidyTreeModel->selectedChecks());
1102     updateConfig(config);
1103 }
1104 
onClazyTreeChanged()1105 void DiagnosticConfigsWidget::onClazyTreeChanged()
1106 {
1107     syncClazyChecksGroupBox();
1108 
1109     ClangDiagnosticConfig config = currentConfig();
1110     if (config.clazyMode() == ClangDiagnosticConfig::ClazyMode::UseDefaultChecks)
1111         config.setClazyMode(ClangDiagnosticConfig::ClazyMode::UseCustomChecks);
1112     config.setClazyChecks(m_clazyTreeModel->enabledChecks().join(","));
1113     updateConfig(config);
1114 }
1115 
syncClazyChecksGroupBox()1116 void DiagnosticConfigsWidget::syncClazyChecksGroupBox()
1117 {
1118     const auto isHidden = [this](const QModelIndex &index) {
1119         return !m_clazySortFilterProxyModel->filterAcceptsRow(index.row(), index.parent());
1120     };
1121     const bool hasEnabledButHidden = m_clazyTreeModel->hasEnabledButNotVisibleChecks(isHidden);
1122     const int checksCount = m_clazyTreeModel->enabledChecks().count();
1123     const QString title = hasEnabledButHidden ? tr("Checks (%n enabled, some are filtered out)",
1124                                                    nullptr, checksCount)
1125                                               : tr("Checks (%n enabled)", nullptr, checksCount);
1126     m_clazyChecks->checksGroupBox->setTitle(title);
1127 }
1128 
removeClangTidyCheck(const QString & checks,const QString & check)1129 QString removeClangTidyCheck(const QString &checks, const QString &check)
1130 {
1131     const ClangTidyInfo tidyInfo(clangTidyExecutable());
1132     TidyChecksTreeModel model(tidyInfo.supportedChecks);
1133     model.selectChecks(checks);
1134     const QModelIndex index = model.indexForName(check);
1135     if (!index.isValid())
1136         return checks;
1137     model.setData(index, false, Qt::CheckStateRole);
1138     return model.selectedChecks();
1139 }
1140 
removeClazyCheck(const QString & checks,const QString & check)1141 QString removeClazyCheck(const QString &checks, const QString &check)
1142 {
1143     const ClazyStandaloneInfo clazyInfo = ClazyStandaloneInfo::getInfo(clazyStandaloneExecutable());
1144     ClazyChecksTreeModel model(clazyInfo.supportedChecks);
1145     model.enableChecks(checks.split(',', Qt::SkipEmptyParts));
1146     const QModelIndex index = model.indexForName(check.mid(QString("clazy-").length()));
1147     if (!index.isValid())
1148         return checks;
1149     model.setData(index, false, Qt::CheckStateRole);
1150     return model.enabledChecks().join(',');
1151 }
1152 
disableChecks(const QList<Diagnostic> & diagnostics)1153 void disableChecks(const QList<Diagnostic> &diagnostics)
1154 {
1155     if (diagnostics.isEmpty())
1156         return;
1157 
1158     ClangToolsSettings * const settings = ClangToolsSettings::instance();
1159     ClangDiagnosticConfigs configs = settings->diagnosticConfigs();
1160     Utils::Id activeConfigId = settings->runSettings().diagnosticConfigId();
1161     ClangToolsProjectSettings::ClangToolsProjectSettingsPtr projectSettings;
1162 
1163     if (ProjectExplorer::Project *project = ProjectExplorer::SessionManager::projectForFile(
1164             diagnostics.first().location.filePath)) {
1165         projectSettings = ClangToolsProjectSettings::getSettings(project);
1166         if (!projectSettings->useGlobalSettings())
1167             activeConfigId = projectSettings->runSettings().diagnosticConfigId();
1168     }
1169     ClangDiagnosticConfig config = Utils::findOrDefault(configs,
1170         [activeConfigId](const ClangDiagnosticConfig &c) { return c.id() == activeConfigId; });
1171     const bool defaultWasActive = !config.id().isValid();
1172     if (defaultWasActive) {
1173         QTC_ASSERT(configs.isEmpty(), return);
1174         config = builtinConfig();
1175         config.setIsReadOnly(false);
1176         config.setId(Utils::Id::fromString(QUuid::createUuid().toString()));
1177         config.setDisplayName(QCoreApplication::translate("Clang Tools", "Custom Configuration"));
1178         configs << config;
1179         RunSettings runSettings = settings->runSettings();
1180         runSettings.setDiagnosticConfigId(config.id());
1181         settings->setRunSettings(runSettings);
1182         if (projectSettings && !projectSettings->useGlobalSettings()) {
1183             runSettings = projectSettings->runSettings();
1184             runSettings.setDiagnosticConfigId(config.id());
1185             projectSettings->setRunSettings(runSettings);
1186         }
1187     }
1188 
1189     for (const Diagnostic &diag : diagnostics) {
1190         if (diag.name.startsWith("clazy-")) {
1191             if (config.clazyMode() == ClangDiagnosticConfig::ClazyMode::UseDefaultChecks) {
1192                 config.setClazyMode(ClangDiagnosticConfig::ClazyMode::UseCustomChecks);
1193                 const ClazyStandaloneInfo clazyInfo
1194                         = ClazyStandaloneInfo::getInfo(clazyStandaloneExecutable());
1195                 config.setClazyChecks(clazyInfo.defaultChecks.join(','));
1196             }
1197             config.setClazyChecks(removeClazyCheck(config.clazyChecks(), diag.name));
1198         } else if (config.clangTidyMode() != ClangDiagnosticConfig::TidyMode::UseConfigFile) {
1199             if (config.clangTidyMode() == ClangDiagnosticConfig::TidyMode::UseDefaultChecks) {
1200                 config.setClangTidyMode(ClangDiagnosticConfig::TidyMode::UseCustomChecks);
1201                 const ClangTidyInfo tidyInfo(clangTidyExecutable());
1202                 config.setClangTidyChecks(tidyInfo.defaultChecks.join(','));
1203             }
1204             config.setClangTidyChecks(removeClangTidyCheck(config.clangTidyChecks(), diag.name));
1205         }
1206     }
1207 
1208     if (!defaultWasActive) {
1209         for (ClangDiagnosticConfig &c : configs) {
1210             if (c.id() == config.id()) {
1211                 c = config;
1212                 break;
1213             }
1214         }
1215     }
1216     settings->setDiagnosticConfigs(configs);
1217     settings->writeSettings();
1218 }
1219 
1220 } // namespace Internal
1221 } // namespace ClangTools
1222 
1223 #include "diagnosticconfigswidget.moc"
1224