diff --git a/src/libs/qmljs/qmljscheck.cpp b/src/libs/qmljs/qmljscheck.cpp index 8d5bcadad57..51be467387d 100644 --- a/src/libs/qmljs/qmljscheck.cpp +++ b/src/libs/qmljs/qmljscheck.cpp @@ -12,6 +12,7 @@ #include #include +#include #include #include @@ -639,7 +640,46 @@ Q_GLOBAL_STATIC(UnsupportedRootObjectTypesByVisualDesigner, unsupportedRootObjec Q_GLOBAL_STATIC(UnsupportedRootObjectTypesByQmlUi, unsupportedRootObjectTypesByQmlUi) Q_GLOBAL_STATIC(UnsupportedTypesByQmlUi, unsupportedTypesByQmlUi) -Check::Check(Document::Ptr doc, const ContextPtr &context) +QList Check::defaultDisabledMessages() +{ + static const QList disabled = Utils::sorted(QList{ + HintAnonymousFunctionSpacing, + HintDeclareVarsInOneLine, + HintDeclarationsShouldBeAtStartOfFunction, + HintBinaryOperatorSpacing, + HintOneStatementPerLine, + HintExtraParentheses, + + // QmlDesigner related + WarnImperativeCodeNotEditableInVisualDesigner, + WarnUnsupportedTypeInVisualDesigner, + WarnReferenceToParentItemNotSupportedByVisualDesigner, + WarnUndefinedValueForVisualDesigner, + WarnStatesOnlyInRootItemForVisualDesigner, + ErrUnsupportedRootTypeInVisualDesigner, + ErrInvalidIdeInVisualDesigner, + + }); + return disabled; +} + +QList Check::defaultDisabledMessagesForNonQuickUi() +{ + static const QList disabled = Utils::sorted(QList{ + // QmlDesigner related + ErrUnsupportedRootTypeInQmlUi, + ErrUnsupportedTypeInQmlUi, + ErrFunctionsNotSupportedInQmlUi, + ErrBlocksNotSupportedInQmlUi, + ErrBehavioursNotSupportedInQmlUi, + ErrStatesOnlyInRootItemInQmlUi, + ErrReferenceToParentItemNotSupportedInQmlUi, + ErrDoNotMixTranslationFunctionsInQmlUi, + }); + return disabled; +} + +Check::Check(Document::Ptr doc, const ContextPtr &context, Utils::QtcSettings *qtcSettings) : _doc(doc) , _context(context) , _scopeChain(doc, _context) @@ -655,16 +695,25 @@ Check::Check(Document::Ptr doc, const ContextPtr &context) } _enabledMessages = Utils::toSet(Message::allMessageTypes()); - disableMessage(HintAnonymousFunctionSpacing); - disableMessage(HintDeclareVarsInOneLine); - disableMessage(HintDeclarationsShouldBeAtStartOfFunction); - disableMessage(HintBinaryOperatorSpacing); - disableMessage(HintOneStatementPerLine); - disableMessage(HintExtraParentheses); + if (qtcSettings && qtcSettings->value("J.QtQuick/QmlJSEditor.useCustomAnalyzer").toBool()) { + auto disabled = qtcSettings->value("J.QtQuick/QmlJSEditor.disabledMessages").toList(); + for (const QVariant &disabledNumber : disabled) + disableMessage(StaticAnalysis::Type(disabledNumber.toInt())); - disableQmlDesignerChecks(); - if (!isQtQuick2Ui()) - disableQmlDesignerUiFileChecks(); + if (!isQtQuick2Ui()) { + auto disabled = qtcSettings->value("J.QtQuick/QmlJSEditor.disabledMessagesNonQuickUI").toList(); + for (const QVariant &disabledNumber : disabled) + disableMessage(StaticAnalysis::Type(disabledNumber.toInt())); + } + } else { + for (auto type : defaultDisabledMessages()) + disableMessage(type); + + if (!isQtQuick2Ui()) { + for (auto type : defaultDisabledMessagesForNonQuickUi()) + disableMessage(type); + } + } } Check::~Check() @@ -702,17 +751,6 @@ void Check::enableQmlDesignerChecks() //## triggers too often ## check.enableMessage(StaticAnalysis::WarnUndefinedValueForVisualDesigner); } -void Check::disableQmlDesignerChecks() -{ - disableMessage(WarnImperativeCodeNotEditableInVisualDesigner); - disableMessage(WarnUnsupportedTypeInVisualDesigner); - disableMessage(WarnReferenceToParentItemNotSupportedByVisualDesigner); - disableMessage(WarnUndefinedValueForVisualDesigner); - disableMessage(WarnStatesOnlyInRootItemForVisualDesigner); - disableMessage(ErrUnsupportedRootTypeInVisualDesigner); - disableMessage(ErrInvalidIdeInVisualDesigner); -} - void Check::enableQmlDesignerUiFileChecks() { enableMessage(ErrUnsupportedRootTypeInQmlUi); diff --git a/src/libs/qmljs/qmljscheck.h b/src/libs/qmljs/qmljscheck.h index a868bbe15e2..60484fc43f1 100644 --- a/src/libs/qmljs/qmljscheck.h +++ b/src/libs/qmljs/qmljscheck.h @@ -12,6 +12,8 @@ #include #include +namespace Utils { class QtcSettings; } + namespace QmlJS { class Imports; @@ -22,7 +24,7 @@ class QMLJS_EXPORT Check: protected AST::Visitor public: // prefer taking root scope chain? - Check(Document::Ptr doc, const ContextPtr &context); + Check(Document::Ptr doc, const ContextPtr &context, Utils::QtcSettings *qtcSettings = nullptr); ~Check(); QList operator()(); @@ -31,11 +33,13 @@ public: void disableMessage(StaticAnalysis::Type type); void enableQmlDesignerChecks(); - void disableQmlDesignerChecks(); void enableQmlDesignerUiFileChecks(); void disableQmlDesignerUiFileChecks(); + static QList defaultDisabledMessages(); + static QList defaultDisabledMessagesForNonQuickUi(); + protected: bool preVisit(AST::Node *ast) override; void postVisit(AST::Node *ast) override; diff --git a/src/plugins/qmljseditor/qmljseditingsettingspage.cpp b/src/plugins/qmljseditor/qmljseditingsettingspage.cpp index 7b6474449c4..1a8ff90e552 100644 --- a/src/plugins/qmljseditor/qmljseditingsettingspage.cpp +++ b/src/plugins/qmljseditor/qmljseditingsettingspage.cpp @@ -7,11 +7,15 @@ #include #include +#include +#include +#include +#include #include #include #include +#include #include -#include #include #include @@ -19,6 +23,7 @@ #include #include #include +#include const char AUTO_FORMAT_ON_SAVE[] = "QmlJSEditor.AutoFormatOnSave"; const char AUTO_FORMAT_ONLY_CURRENT_PROJECT[] = "QmlJSEditor.AutoFormatOnlyCurrentProject"; @@ -31,6 +36,9 @@ const char UIQML_OPEN_MODE[] = "QmlJSEditor.openUiQmlMode"; const char FORMAT_COMMAND[] = "QmlJSEditor.formatCommand"; const char FORMAT_COMMAND_OPTIONS[] = "QmlJSEditor.formatCommandOptions"; const char CUSTOM_COMMAND[] = "QmlJSEditor.useCustomFormatCommand"; +const char CUSTOM_ANALYZER[] = "QmlJSEditor.useCustomAnalyzer"; +const char DISABLED_MESSAGES[] = "QmlJSEditor.disabledMessages"; +const char DISABLED_MESSAGES_NONQUICKUI[] = "QmlJSEditor.disabledMessagesNonQuickUI"; const char DEFAULT_CUSTOM_FORMAT_COMMAND[] = "%{CurrentDocument:Project:QT_HOST_BINS}/qmlformat"; using namespace QmlJSEditor; @@ -57,6 +65,23 @@ void QmlJsEditingSettings::fromSettings(QSettings *settings) m_formatCommand = settings->value(FORMAT_COMMAND, {}).toString(); m_formatCommandOptions = settings->value(FORMAT_COMMAND_OPTIONS, {}).toString(); m_useCustomFormatCommand = settings->value(CUSTOM_COMMAND, QVariant(false)).toBool(); + m_useCustomAnalyzer = settings->value(CUSTOM_ANALYZER, QVariant(false)).toBool(); + + const QList disabledByDefault = Utils::transform( + QmlJS::Check::defaultDisabledMessages(), + [](QmlJS::StaticAnalysis::Type t) { return int(t); }); + m_disabledMessages = Utils::transform( + settings->value(DISABLED_MESSAGES, + QVariant::fromValue(disabledByDefault)).toList(), + [](const QVariant &v){ return v.toInt(); }); + const QList disabledForNonQuickUi = Utils::transform( + QmlJS::Check::defaultDisabledMessagesForNonQuickUi(), + [](QmlJS::StaticAnalysis::Type t){ return int(t); }); + m_disabledMessagesForNonQuickUi = Utils::transform( + settings->value(DISABLED_MESSAGES_NONQUICKUI, + QVariant::fromValue(disabledForNonQuickUi)).toList(), + [](const QVariant &v) { return v.toInt(); }); + settings->endGroup(); } @@ -80,6 +105,24 @@ void QmlJsEditingSettings::toSettings(QSettings *settings) const CUSTOM_COMMAND, m_useCustomFormatCommand, false); + Utils::QtcSettings::setValueWithDefault(settings, + CUSTOM_ANALYZER, + m_useCustomAnalyzer, + false); + const QList disabledByDefault = Utils::transform( + QmlJS::Check::defaultDisabledMessages(), + [](QmlJS::StaticAnalysis::Type t){ return int(t); }); + Utils::QtcSettings::setValueWithDefault(settings, + DISABLED_MESSAGES, + Utils::sorted(Utils::toList(m_disabledMessages)), + disabledByDefault); + const QList disabledNonQuickUi = Utils::transform( + QmlJS::Check::defaultDisabledMessagesForNonQuickUi(), + [](QmlJS::StaticAnalysis::Type t){ return int(t); }); + Utils::QtcSettings::setValueWithDefault(settings, + DISABLED_MESSAGES_NONQUICKUI, + Utils::sorted(Utils::toList(m_disabledMessagesForNonQuickUi)), + disabledNonQuickUi); settings->endGroup(); QmllsSettingsManager::instance()->checkForChanges(); } @@ -93,7 +136,10 @@ bool QmlJsEditingSettings::equals(const QmlJsEditingSettings &other) const && m_foldAuxData == other.m_foldAuxData && m_qmllsSettings == other.m_qmllsSettings && m_uiQmlOpenMode == other.m_uiQmlOpenMode && m_formatCommand == other.m_formatCommand && m_formatCommandOptions == other.m_formatCommandOptions - && m_useCustomFormatCommand == other.m_useCustomFormatCommand; + && m_useCustomFormatCommand == other.m_useCustomFormatCommand + && m_useCustomAnalyzer == other.m_useCustomAnalyzer + && m_disabledMessages == other.m_disabledMessages + && m_disabledMessagesForNonQuickUi == other.m_disabledMessagesForNonQuickUi; } bool QmlJsEditingSettings::enableContextPane() const @@ -201,6 +247,92 @@ void QmlJsEditingSettings::setUiQmlOpenMode(const QString &mode) m_uiQmlOpenMode = mode; } +bool QmlJsEditingSettings::useCustomAnalyzer() const +{ + return m_useCustomAnalyzer; +} + +void QmlJsEditingSettings::setUseCustomAnalyzer(bool customAnalyzer) +{ + m_useCustomAnalyzer = customAnalyzer; +} + +QSet QmlJsEditingSettings::disabledMessages() const +{ + return m_disabledMessages; +} + +void QmlJsEditingSettings::setDisabledMessages(const QSet &disabled) +{ + m_disabledMessages = disabled; +} + +QSet QmlJsEditingSettings::disabledMessagesForNonQuickUi() const +{ + return m_disabledMessagesForNonQuickUi; +} + +void QmlJsEditingSettings::setDisabledMessagesForNonQuickUi(const QSet &disabled) +{ + m_disabledMessagesForNonQuickUi = disabled; +} + +class AnalyzerMessageItem final : public Utils::TreeItem +{ +public: + AnalyzerMessageItem() = default; + AnalyzerMessageItem(int number, const QString &message) + : m_messageNumber(number) + , m_message(message) + {} + + QVariant data(int column, int role) const final + { + if (role == Qt::DisplayRole) { + if (column == 0) + return QString("M%1").arg(m_messageNumber); + if (column == 2) + return m_message.split('\n').first(); + } else if (role == Qt::CheckStateRole) { + if (column == 0) + return m_checked ? Qt::Checked : Qt::Unchecked; + if (column == 1) + return m_disabledInNonQuickUi ? Qt::Checked : Qt::Unchecked; + } + return TreeItem::data(column, role); + } + + bool setData(int column, const QVariant &value, int role) final + { + if (role == Qt::CheckStateRole) { + if (column == 0) { + m_checked = value.toBool(); + return true; + } + if (column == 1) { + m_disabledInNonQuickUi = value.toBool(); + return true; + } + } + return false; + } + + Qt::ItemFlags flags(int column) const final + { + if (column == 0 || column == 1) + return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable; + else + return Qt::ItemIsEnabled | Qt::ItemIsSelectable; + } + + int messageNumber() const { return m_messageNumber; } +private: + int m_messageNumber = -1; + QString m_message; + bool m_checked = true; + bool m_disabledInNonQuickUi = false; +}; + class QmlJsEditingSettingsPageWidget final : public Core::IOptionsPageWidget { public: @@ -247,6 +379,22 @@ public: useLatestQmlls->setEnabled(checked != Qt::Unchecked); }); + useCustomAnalyzer = new QCheckBox(Tr::tr("Use customized static analyzer")); + useCustomAnalyzer->setChecked(s.useCustomAnalyzer()); + analyzerMessageModel = new Utils::TreeModel(this); + analyzerMessageModel->setHeader({Tr::tr("Enabled"), + Tr::tr("Disabled for non Qt Quick UI"), + Tr::tr("Message")}); + analyzerMessagesView = new QTreeView; + analyzerMessagesView->setModel(analyzerMessageModel); + analyzerMessagesView->setEnabled(s.useCustomAnalyzer()); + QObject::connect(useCustomAnalyzer, &QCheckBox::stateChanged, this, [this](int checked){ + analyzerMessagesView->setEnabled(checked != Qt::Unchecked); + }); + analyzerMessagesView->setToolTip(Tr::tr("Enabled checks can be disabled for non Qt Quick UI" + " files,\nbut disabled checks cannot get explicitly" + " enabled for non Qt Quick UI files.")); + using namespace Layouting; // clang-format off QWidget *formattingGroup = nullptr; @@ -279,6 +427,10 @@ public: title(Tr::tr("Language Server")), Column{useQmlls, useLatestQmlls}, }, + Group { + title(Tr::tr("Static Analyzer")), + Column{ useCustomAnalyzer, analyzerMessagesView }, + }, st, }.attachTo(this); // clang-format on @@ -300,6 +452,8 @@ public: updateFormatCommandState(); }); connect(useCustomFormatCommand, &QCheckBox::toggled, this, updateFormatCommandState); + + populateAnalyzerMessages(s.disabledMessages(), s.disabledMessagesForNonQuickUi()); } void apply() final @@ -316,10 +470,39 @@ public: s.setUiQmlOpenMode(uiQmlOpenComboBox->currentData().toString()); s.qmllsSettings().useQmlls = useQmlls->isChecked(); s.qmllsSettings().useLatestQmlls = useLatestQmlls->isChecked(); + s.setUseCustomAnalyzer(useCustomAnalyzer->isChecked()); + QSet disabled; + QSet disabledForNonQuickUi; + analyzerMessageModel->forAllItems( + [&disabled, &disabledForNonQuickUi](AnalyzerMessageItem *item){ + if (item->data(0, Qt::CheckStateRole) == Qt::Unchecked) + disabled.insert(item->messageNumber()); + if (item->data(1, Qt::CheckStateRole) == Qt::Checked) + disabledForNonQuickUi.insert(item->messageNumber()); + }); + s.setDisabledMessages(disabled); + s.setDisabledMessagesForNonQuickUi(disabledForNonQuickUi); s.set(); } private: + void populateAnalyzerMessages(const QSet &disabled, const QSet &disabledForNonQuickUi) + { + using namespace QmlJS::StaticAnalysis; + auto knownMessages = Utils::sorted(Message::allMessageTypes()); + auto root = analyzerMessageModel->rootItem(); + for (auto msgType : knownMessages) { + const QString msg = Message::prototypeForMessageType(msgType).message; + auto item = new AnalyzerMessageItem(msgType, msg); + item->setData(0, !disabled.contains(msgType), Qt::CheckStateRole); + item->setData(1, disabledForNonQuickUi.contains(msgType), Qt::CheckStateRole); + root->appendChild(item); + } + + for (int column = 0; column < 3; ++column) + analyzerMessagesView->resizeColumnToContents(column); + } + QCheckBox *autoFormatOnSave; QCheckBox *autoFormatOnlyCurrentProject; QCheckBox *useCustomFormatCommand; @@ -331,6 +514,9 @@ private: QCheckBox *useQmlls; QCheckBox *useLatestQmlls; QComboBox *uiQmlOpenComboBox; + QCheckBox *useCustomAnalyzer; + QTreeView *analyzerMessagesView; + Utils::TreeModel *analyzerMessageModel; }; diff --git a/src/plugins/qmljseditor/qmljseditingsettingspage.h b/src/plugins/qmljseditor/qmljseditingsettingspage.h index dc6bf1e9fd9..d7f8f78bd30 100644 --- a/src/plugins/qmljseditor/qmljseditingsettingspage.h +++ b/src/plugins/qmljseditor/qmljseditingsettingspage.h @@ -58,6 +58,15 @@ public: const QString uiQmlOpenMode() const; void setUiQmlOpenMode(const QString &mode); + bool useCustomAnalyzer() const; + void setUseCustomAnalyzer(bool customAnalyzer); + + QSet disabledMessages() const; + void setDisabledMessages(const QSet &disabled); + + QSet disabledMessagesForNonQuickUi() const; + void setDisabledMessagesForNonQuickUi(const QSet &disabled); + friend bool operator==(const QmlJsEditingSettings &s1, const QmlJsEditingSettings &s2) { return s1.equals(s2); } friend bool operator!=(const QmlJsEditingSettings &s1, const QmlJsEditingSettings &s2) @@ -70,10 +79,13 @@ private: bool m_autoFormatOnlyCurrentProject = false; bool m_foldAuxData = true; bool m_useCustomFormatCommand = false; + bool m_useCustomAnalyzer = false; QmllsSettings m_qmllsSettings; QString m_uiQmlOpenMode; QString m_formatCommand; QString m_formatCommandOptions; + QSet m_disabledMessages; + QSet m_disabledMessagesForNonQuickUi; }; namespace Internal { diff --git a/src/plugins/qmljseditor/qmljssemanticinfoupdater.cpp b/src/plugins/qmljseditor/qmljssemanticinfoupdater.cpp index e0a20c18451..764f125e543 100644 --- a/src/plugins/qmljseditor/qmljssemanticinfoupdater.cpp +++ b/src/plugins/qmljseditor/qmljssemanticinfoupdater.cpp @@ -104,7 +104,7 @@ QmlJSTools::SemanticInfo SemanticInfoUpdater::makeNewSemanticInfo(const QmlJS::D semanticInfo.staticAnalysisMessages = jsonChecker(schema); } } else { - Check checker(doc, semanticInfo.context); + Check checker(doc, semanticInfo.context, Core::ICore::settings()); semanticInfo.staticAnalysisMessages = checker(); } diff --git a/src/plugins/qmljseditor/qmltaskmanager.cpp b/src/plugins/qmljseditor/qmltaskmanager.cpp index fb673066f3a..ca985f4022e 100644 --- a/src/plugins/qmljseditor/qmltaskmanager.cpp +++ b/src/plugins/qmljseditor/qmltaskmanager.cpp @@ -4,6 +4,7 @@ #include "qmltaskmanager.h" #include "qmljseditorconstants.h" +#include #include #include #include @@ -87,7 +88,7 @@ void QmlTaskManager::collectMessages(QPromise &promise, fileName, Constants::TASK_CATEGORY_QML_ANALYSIS); - Check checker(document, context); + Check checker(document, context, Core::ICore::settings()); result.tasks += convertToTasks(checker(), fileName, Constants::TASK_CATEGORY_QML_ANALYSIS);