aboutsummaryrefslogtreecommitdiffstats
path: root/tools
diff options
context:
space:
mode:
authorMaximilian Goldstein <max.goldstein@qt.io>2021-05-12 18:32:57 +0200
committerMaximilian Goldstein <max.goldstein@qt.io>2021-05-27 11:30:12 +0200
commit73d51eed2f669ac91f139af3d6a5bcc621dec64a (patch)
tree245f36c09ac67f0b87ca3f15e5f249595bfb9603 /tools
parent769fa5f8fae7b40cf38de086dcac6b73b32364e8 (diff)
qmllint: Add support for loading options from settings
This change adds support for a reading a simple settings file (.qmllint.ini) to set log levels and various other options. The settings file applies on a per-directory basis so linting files in two subdirectories with different settings will use their respective settings files. If the directory of the linted file does not contain any settings we search through all parent directories. This is implemented in a way that qmlformat can also utilize the settings file code. This makes qmllint more useful for larger projects that might want different settings for different parts of their QML code. It also allows for better integration in CI checks and pre-commit hooks. [ChangeLog][General][qmllint] Adds the ability to set linting options via a settings file rather than using command line parameters. Use --write-defaults to generate a template with default values for editing. Use --ignore-settings to disable this feature Change-Id: I94e4a47916b5dfd16c3a69efdba3858235cab738 Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org> Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
Diffstat (limited to 'tools')
-rw-r--r--tools/qmllint/CMakeLists.txt2
-rw-r--r--tools/qmllint/main.cpp82
-rw-r--r--tools/shared/qqmltoolingsettings.cpp148
-rw-r--r--tools/shared/qqmltoolingsettings.h58
4 files changed, 274 insertions, 16 deletions
diff --git a/tools/qmllint/CMakeLists.txt b/tools/qmllint/CMakeLists.txt
index 77e22598f3..8e9a737f5a 100644
--- a/tools/qmllint/CMakeLists.txt
+++ b/tools/qmllint/CMakeLists.txt
@@ -12,6 +12,8 @@ qt_internal_add_tool(${target_name}
checkidentifiers.cpp checkidentifiers.h
findwarnings.cpp findwarnings.h
main.cpp
+ ../shared/qqmltoolingsettings.h
+ ../shared/qqmltoolingsettings.cpp
PUBLIC_LIBRARIES
Qt::CorePrivate
Qt::QmlCompilerPrivate
diff --git a/tools/qmllint/main.cpp b/tools/qmllint/main.cpp
index 05bcc0fa13..99294ca08a 100644
--- a/tools/qmllint/main.cpp
+++ b/tools/qmllint/main.cpp
@@ -27,6 +27,7 @@
****************************************************************************/
#include "findwarnings.h"
+#include "../shared/qqmltoolingsettings.h"
#include <QtQmlCompiler/private/qqmljsresourcefilemapper_p.h>
@@ -125,6 +126,7 @@ int main(int argv, char *argc[])
QCoreApplication::setApplicationVersion("1.0");
#if QT_CONFIG(commandlineparser)
QCommandLineParser parser;
+ QQmlToolingSettings settings(QLatin1String("qmllint"));
parser.setApplicationDescription(QLatin1String(R"(QML syntax verifier and analyzer
All warnings can be set to three levels:
@@ -139,9 +141,26 @@ All warnings can be set to three levels:
QLatin1String("Don't output syntax errors"));
parser.addOption(silentOption);
+ QCommandLineOption writeDefaultsOption(
+ QStringList() << "write-defaults",
+ QLatin1String("Writes defaults settings to .qmllint.ini and exits (Warning: This "
+ "will overwrite any existing settings and comments!)"));
+ parser.addOption(writeDefaultsOption);
+
+ QCommandLineOption ignoreSettings(QStringList() << "ignore-settings",
+ QLatin1String("Ignores all settings files and only takes "
+ "command line options into consideration"));
+ parser.addOption(ignoreSettings);
+
for (auto it = options.cbegin(); it != options.cend(); ++it) {
- QCommandLineOption option(it.key(), it.value().m_description + QStringLiteral(" (default: %1)").arg(it.value().levelToString()) , QStringLiteral("level"));
+ QCommandLineOption option(
+ it.key(),
+ it.value().m_description
+ + QStringLiteral(" (default: %1)").arg(it.value().levelToString()),
+ QStringLiteral("level"), it.value().levelToString());
parser.addOption(option);
+ settings.addOption(QStringLiteral("Warnings/") + it.value().m_settingsName,
+ it.value().levelToString());
}
// TODO: Remove after Qt 6.2
@@ -162,6 +181,8 @@ All warnings can be set to three levels:
QStringLiteral("Look for related files in the given resource file"),
QStringLiteral("resource"));
parser.addOption(resourceOption);
+ const QString &resourceSetting = QLatin1String("ResourcePath");
+ settings.addOption(resourceSetting);
QCommandLineOption qmlImportPathsOption(
QStringList() << "I"
@@ -169,39 +190,56 @@ All warnings can be set to three levels:
QLatin1String("Look for QML modules in specified directory"),
QLatin1String("directory"));
parser.addOption(qmlImportPathsOption);
+ const QString qmlImportPathsSetting = QLatin1String("AdditionalQmlImportPaths");
+ settings.addOption(qmlImportPathsSetting);
QCommandLineOption qmlImportNoDefault(
QStringList() << "bare",
QLatin1String("Do not include default import directories or the current directory. "
"This may be used to run qmllint on a project using a different Qt version."));
parser.addOption(qmlImportNoDefault);
+ settings.addOption(QLatin1String("DisableDefaultImports"), false);
QCommandLineOption qmltypesFilesOption(
QStringList() << "i"
<< "qmltypes",
- QLatin1String("Include the specified qmltypes files. By default, all qmltypes files "
+ QLatin1String("Import the specified qmltypes files. By default, all qmltypes files "
"found in the current directory are used. When this option is set, you "
"have to explicitly add files from the current directory if you want "
"them to be used."),
QLatin1String("qmltypes"));
parser.addOption(qmltypesFilesOption);
+ const QString qmltypesFilesSetting = QLatin1String("OverwriteImportTypes");
+ settings.addOption(qmltypesFilesSetting);
parser.addPositionalArgument(QLatin1String("files"),
QLatin1String("list of qml or js files to verify"));
parser.process(app);
- for (auto it = options.begin(); it != options.end(); ++it) {
- if (parser.isSet(it.key())) {
- const QString value = parser.value(it.key());
- auto &option = it.value();
+ if (parser.isSet(writeDefaultsOption)) {
+ return settings.writeDefaults() ? 0 : 1;
+ }
- if (!option.setLevel(value)) {
- qWarning() << "Invalid logging level" << value << "provided for" << it.key() << "(allowed are: disable, info, warning)";
- parser.showHelp(-1);
+ auto updateLogLevels = [&]() {
+ for (auto it = options.begin(); it != options.end(); ++it) {
+ const QString &key = it.key();
+ const QString &settingsName = QStringLiteral("Warnings/") + it.value().m_settingsName;
+ if (parser.isSet(key) || settings.isSet(settingsName)) {
+ const QString value = parser.isSet(key) ? parser.value(key)
+ : settings.value(settingsName).toString();
+ auto &option = it.value();
+
+ if (!option.setLevel(value)) {
+ qWarning() << "Invalid logging level" << value << "provided for" << it.key()
+ << "(allowed are: disable, info, warning)";
+ parser.showHelp(-1);
+ }
}
}
- }
+ };
+
+ updateLogLevels();
const auto positionalArguments = parser.positionalArguments();
if (positionalArguments.isEmpty()) {
@@ -242,9 +280,14 @@ All warnings can be set to three levels:
if (parser.isSet(qmlImportPathsOption))
qmlImportPaths << parser.values(qmlImportPathsOption);
+ qmlImportPaths << settings.value(qmlImportPathsSetting).toStringList();
+
QStringList qmltypesFiles;
if (parser.isSet(qmltypesFilesOption)) {
qmltypesFiles = parser.values(qmltypesFilesOption);
+ } else if (settings.isSet(qmltypesFilesSetting)
+ && !settings.value(qmltypesFilesSetting).toStringList().isEmpty()) {
+ qmltypesFiles = parser.values(qmltypesFilesSetting);
} else {
// If none are given explicitly, use the qmltypes files from the current directory.
QDirIterator it(".", {"*.qmltypes"}, QDir::Files);
@@ -254,7 +297,11 @@ All warnings can be set to three levels:
}
}
- const QString resourceFile = parser.value(resourceOption);
+ QString resourceFile;
+ if (parser.isSet(resourceOption))
+ resourceFile = parser.value(resourceOption);
+ else if (settings.isSet(resourceSetting))
+ resourceFile = settings.value(resourceSetting).toString();
#else
bool silent = false;
@@ -268,13 +315,16 @@ All warnings can be set to three levels:
QQmlJSImporter importer(qmlImportPaths, nullptr);
#if QT_CONFIG(commandlineparser)
- for (const QString &filename : positionalArguments)
+ for (const QString &filename : positionalArguments) {
+ if (!parser.isSet(ignoreSettings)) {
+ settings.search(filename);
+ updateLogLevels();
+ }
#else
const auto arguments = app.arguments();
- for (const QString &filename : arguments)
+ for (const QString &filename : arguments) {
#endif
- success &= lint_file(filename, silent, qmlImportPaths, qmltypesFiles, resourceFile, options,
- importer);
-
+ success &= lint_file(filename, silent, qmlImportPaths, qmltypesFiles, resourceFile, options, importer);
+ }
return success ? 0 : -1;
}
diff --git a/tools/shared/qqmltoolingsettings.cpp b/tools/shared/qqmltoolingsettings.cpp
new file mode 100644
index 0000000000..f2c4cd02c6
--- /dev/null
+++ b/tools/shared/qqmltoolingsettings.cpp
@@ -0,0 +1,148 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the tools applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qqmltoolingsettings.h"
+
+#include <algorithm>
+
+#include <QtCore/qdebug.h>
+#include <QtCore/qdir.h>
+#include <QtCore/qfileinfo.h>
+#include <QtCore/qset.h>
+#include <QtCore/qsettings.h>
+#include <QtCore/qstandardpaths.h>
+
+void QQmlToolingSettings::addOption(const QString &name, QVariant defaultValue)
+{
+ m_values[name] = defaultValue;
+}
+
+bool QQmlToolingSettings::read(const QString &settingsFilePath)
+{
+ if (!QFileInfo::exists(settingsFilePath))
+ return false;
+
+ if (m_currentSettingsPath == settingsFilePath)
+ return true;
+
+ QSettings settings(settingsFilePath, QSettings::IniFormat);
+
+ for (const QString &key : settings.allKeys())
+ m_values[key] = settings.value(key).toString();
+
+ m_currentSettingsPath = settingsFilePath;
+
+ return true;
+}
+
+bool QQmlToolingSettings::writeDefaults() const
+{
+ const QString path = QFileInfo(u".%1.ini"_qs.arg(m_toolName)).absoluteFilePath();
+
+ QSettings settings(path, QSettings::IniFormat);
+ for (auto it = m_values.constBegin(); it != m_values.constEnd(); ++it) {
+ settings.setValue(it.key(), it.value().isNull() ? QString() : it.value());
+ }
+
+ settings.sync();
+
+ if (settings.status() != QSettings::NoError) {
+ qWarning() << "Failed to write default settings to" << path
+ << "Error:" << settings.status();
+ return false;
+ }
+
+ qInfo() << "Wrote default settings to" << path;
+ return true;
+}
+
+bool QQmlToolingSettings::search(const QString &path)
+{
+ QFileInfo fileInfo(path);
+ QDir dir(fileInfo.isDir() ? path : fileInfo.dir());
+
+ QSet<QString> dirs;
+
+ const QString settingsFileName = u".%1.ini"_qs.arg(m_toolName);
+
+ while (dir.exists() && dir.isReadable()) {
+ const QString dirPath = dir.absolutePath();
+
+ if (m_seenDirectories.contains(dirPath)) {
+ const QString cachedIniPath = m_seenDirectories[dirPath];
+ if (cachedIniPath.isEmpty())
+ return false;
+
+ return read(cachedIniPath);
+ }
+
+ dirs << dirPath;
+
+ const QString iniFile = dir.absoluteFilePath(settingsFileName);
+
+ if (read(iniFile)) {
+ for (const QString &dir : qAsConst(dirs))
+ m_seenDirectories[dir] = iniFile;
+ return true;
+ }
+
+ if (!dir.cdUp())
+ break;
+ }
+
+ if (const QString iniFile = QStandardPaths::locate(QStandardPaths::GenericConfigLocation, u"%1.ini"_qs.arg(m_toolName)); !iniFile.isEmpty()) {
+ if (read(iniFile)) {
+ for (const QString &dir : qAsConst(dirs))
+ m_seenDirectories[dir] = iniFile;
+ return true;
+ }
+ }
+
+ // No INI file found anywhere, record the failure so we won't have to traverse the entire
+ // filesystem again
+ for (const QString &dir : qAsConst(dirs))
+ m_seenDirectories[dir] = QString();
+
+ return false;
+}
+
+QVariant QQmlToolingSettings::value(QString name) const
+{
+ return m_values.value(name);
+}
+
+bool QQmlToolingSettings::isSet(QString name) const
+{
+ if (!m_values.contains(name))
+ return false;
+
+ QVariant variant = m_values[name];
+
+ // Unset is encoded as an empty string
+ return !(variant.canConvert(QMetaType(QMetaType::QString)) && variant.toString().isEmpty());
+}
diff --git a/tools/shared/qqmltoolingsettings.h b/tools/shared/qqmltoolingsettings.h
new file mode 100644
index 0000000000..8cb7ea3ff5
--- /dev/null
+++ b/tools/shared/qqmltoolingsettings.h
@@ -0,0 +1,58 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the tools applications of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QQMLTOOLINGSETTINGS_H
+#define QQMLTOOLINGSETTINGS_H
+
+#include <QtCore/qstring.h>
+#include <QtCore/qhash.h>
+#include <QtCore/qvariant.h>
+
+class QQmlToolingSettings
+{
+public:
+ QQmlToolingSettings(const QString &toolName) : m_toolName(toolName) { }
+
+ void addOption(const QString &name, const QVariant defaultValue = QVariant());
+
+ bool writeDefaults() const;
+ bool search(const QString &path);
+
+ QVariant value(QString name) const;
+ bool isSet(QString name) const;
+
+private:
+ QString m_toolName;
+ QString m_currentSettingsPath;
+ QHash<QString, QString> m_seenDirectories;
+ QVariantHash m_values;
+
+ bool read(const QString &settingsFilePath);
+};
+
+#endif