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 ¤t, 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