diff options
Diffstat (limited to 'tools')
37 files changed, 4564 insertions, 401 deletions
diff --git a/tools/qml/conf/content/resizeItemToWindow.qml b/tools/qml/conf/content/resizeItemToWindow.qml new file mode 100644 index 0000000000..a645cf8ea9 --- /dev/null +++ b/tools/qml/conf/content/resizeItemToWindow.qml @@ -0,0 +1,70 @@ +/***************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQuick module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** 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. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +*****************************************************************************/ +import QtQuick.Window 2.0 +import QtQuick 2.0 + +Window { + property Item containedObject: null + property bool __resizeGuard: false + onContainedObjectChanged: { + if (containedObject == undefined || containedObject == null) { + visible = false; + return; + } + __resizeGuard = true + width = containedObject.width; + height = containedObject.height; + containedObject.parent = contentItem; + visible = true; + __resizeGuard = false + } + onWidthChanged: if (!__resizeGuard && containedObject) containedObject.width = width + onHeightChanged: if (!__resizeGuard && containedObject) containedObject.height = height +} diff --git a/tools/qml/conf/qtquick.qml b/tools/qml/conf/content/resizeWindowToItem.qml index ef7a46eb2f..cd03e5065a 100644 --- a/tools/qml/conf/qtquick.qml +++ b/tools/qml/conf/content/resizeWindowToItem.qml @@ -1,6 +1,6 @@ /***************************************************************************** ** -** Copyright (C) 2017 The Qt Company Ltd. +** Copyright (C) 2019 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtQuick module of the Qt Toolkit. @@ -57,8 +57,8 @@ Window { visible = false; return; } - width = containedObject.width; - height = containedObject.height; + width = Qt.binding(function() { return containedObject.width }); + height = Qt.binding(function() { return containedObject.height }); containedObject.parent = contentItem; visible = true; } diff --git a/tools/qml/conf/configuration.qml b/tools/qml/conf/default.qml index e12fa39a3e..be44ee79b4 100644 --- a/tools/qml/conf/configuration.qml +++ b/tools/qml/conf/default.qml @@ -1,6 +1,6 @@ /***************************************************************************** ** -** Copyright (C) 2017 The Qt Company Ltd. +** Copyright (C) 2019 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtQuick module of the Qt Toolkit. @@ -52,6 +52,6 @@ import QmlRuntime.Config 1.0 Configuration { PartialScene { itemType: "QQuickItem" - container: "qtquick.qml" + container: "content/resizeItemToWindow.qml" } } diff --git a/tools/qml/conf/resizeToItem.qml b/tools/qml/conf/resizeToItem.qml new file mode 100644 index 0000000000..480995a6b0 --- /dev/null +++ b/tools/qml/conf/resizeToItem.qml @@ -0,0 +1,57 @@ +/***************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtQuick module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** 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. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +*****************************************************************************/ +import QmlRuntime.Config 1.0 + +Configuration { + PartialScene { + itemType: "QQuickItem" + container: "content/resizeWindowToItem.qml" + } +} diff --git a/tools/qml/main.cpp b/tools/qml/main.cpp index 8cfc0eaaac..daa278457d 100644 --- a/tools/qml/main.cpp +++ b/tools/qml/main.cpp @@ -44,9 +44,12 @@ #include <QQmlApplicationEngine> #include <QQmlComponent> +#include <QCommandLineOption> +#include <QCommandLineParser> #include <QDir> #include <QFile> #include <QFileInfo> +#include <QLoggingCategory> #include <QStringList> #include <QScopedPointer> #include <QDebug> @@ -57,6 +60,7 @@ #include <qqml.h> #include <qqmldebug.h> +#include <private/qmemory_p.h> #include <private/qtqmlglobal_p.h> #if QT_CONFIG(qml_animation) #include <private/qabstractanimation_p.h> @@ -65,24 +69,41 @@ #include <cstdio> #include <cstring> #include <cstdlib> - -#define VERSION_MAJ 1 -#define VERSION_MIN 1 -#define VERSION_STR "1.1" +#include <memory> #define FILE_OPEN_EVENT_WAIT_TIME 3000 // ms +enum QmlApplicationType { + QmlApplicationTypeUnknown + , QmlApplicationTypeCore +#ifdef QT_GUI_LIB + , QmlApplicationTypeGui +#ifdef QT_WIDGETS_LIB + , QmlApplicationTypeWidget +#endif // QT_WIDGETS_LIB +#endif // QT_GUI_LIB +}; + +static QmlApplicationType applicationType = +#ifndef QT_GUI_LIB + QmlApplicationTypeCore; +#else + QmlApplicationTypeGui; +#endif // QT_GUI_LIB + static Config *conf = nullptr; static QQmlApplicationEngine *qae = nullptr; #if defined(Q_OS_DARWIN) || defined(QT_GUI_LIB) static int exitTimerId = -1; #endif -bool verboseMode = false; static const QString iconResourcePath(QStringLiteral(":/qt-project.org/QmlRuntime/resources/qml-64.png")); +static const QString confResourcePath(QStringLiteral(":/qt-project.org/QmlRuntime/conf/")); +static bool verboseMode = false; +static bool quietMode = false; static void loadConf(const QString &override, bool quiet) // Terminates app on failure { - const QString defaultFileName = QLatin1String("configuration.qml"); + const QString defaultFileName = QLatin1String("default.qml"); QUrl settingsUrl; bool builtIn = false; //just for keeping track of the warning if (override.isEmpty()) { @@ -92,29 +113,38 @@ static void loadConf(const QString &override, bool quiet) // Terminates app on f settingsUrl = QUrl::fromLocalFile(fi.absoluteFilePath()); } else { // ### If different built-in configs are needed per-platform, just apply QFileSelector to the qrc conf.qml path - settingsUrl = QUrl(QLatin1String("qrc:///qt-project.org/QmlRuntime/conf/") + defaultFileName); + fi.setFile(confResourcePath + defaultFileName); + settingsUrl = QUrl::fromLocalFile(fi.absoluteFilePath()); builtIn = true; } } else { QFileInfo fi; - fi.setFile(override); - if (!fi.exists()) { - printf("qml: Couldn't find required configuration file: %s\n", - qPrintable(QDir::toNativeSeparators(fi.absoluteFilePath()))); - exit(1); + fi.setFile(confResourcePath + override + QLatin1String(".qml")); + if (fi.exists()) { + settingsUrl = QUrl::fromLocalFile(fi.absoluteFilePath()); + builtIn = true; + } else { + fi.setFile(override); + if (!fi.exists()) { + printf("qml: Couldn't find required configuration file: %s\n", + qPrintable(QDir::toNativeSeparators(fi.absoluteFilePath()))); + exit(1); + } + settingsUrl = QUrl::fromLocalFile(fi.absoluteFilePath()); } - settingsUrl = QUrl::fromLocalFile(fi.absoluteFilePath()); } if (!quiet) { printf("qml: %s\n", QLibraryInfo::build()); - if (builtIn) - printf("qml: Using built-in configuration.\n"); - else - printf("qml: Using configuration file: %s\n", + if (builtIn) { + printf("qml: Using built-in configuration: %s\n", + qPrintable(override.isEmpty() ? defaultFileName : override)); + } else { + printf("qml: Using configuration: %s\n", qPrintable(settingsUrl.isLocalFile() ? QDir::toNativeSeparators(settingsUrl.toLocalFile()) : settingsUrl.toString())); + } } // TODO: When we have better engine control, ban QtQuick* imports on this engine @@ -128,9 +158,23 @@ static void loadConf(const QString &override, bool quiet) // Terminates app on f } } -#ifdef QT_GUI_LIB +void noFilesGiven() +{ + if (!quietMode) + printf("qml: No files specified. Terminating.\n"); + exit(1); +} -void noFilesGiven(); +static void listConfFiles() +{ + QDir confResourceDir(confResourcePath); + printf("%s\n", qPrintable(QCoreApplication::translate("main", "Built-in configurations:"))); + for (const QFileInfo &fi : confResourceDir.entryInfoList(QDir::Files)) + printf(" %s\n", qPrintable(fi.baseName())); + exit(0); +} + +#ifdef QT_GUI_LIB // Loads qml after receiving a QFileOpenEvent class LoaderApplication : public QGuiApplication @@ -179,8 +223,8 @@ public: connect(e, &QQmlEngine::exit, this, &LoadWatcher::exit); } - bool earlyExit = false; int returnCode = 0; + bool earlyExit = false; public Q_SLOTS: void checkFinished(QObject *o, const QUrl &url) @@ -221,8 +265,8 @@ private: void checkForWindow(QObject *o); private: - int expectedFileCount; bool haveWindow = false; + int expectedFileCount; }; void LoadWatcher::contain(QObject *o, const QUrl &containPath) @@ -282,93 +326,18 @@ void quietMessageHandler(QtMsgType type, const QMessageLogContext &ctxt, const Q exit(-1); case QtCriticalMsg: case QtDebugMsg: + case QtInfoMsg: case QtWarningMsg: - default: ; } } - -// ### Should command line arguments have translations? Qt creator doesn't, so maybe it's not worth it. -enum QmlApplicationType { - QmlApplicationTypeUnknown - , QmlApplicationTypeCore -#ifdef QT_GUI_LIB - , QmlApplicationTypeGui -#ifdef QT_WIDGETS_LIB - , QmlApplicationTypeWidget -#endif // QT_WIDGETS_LIB -#endif // QT_GUI_LIB -}; - -#ifndef QT_GUI_LIB -QmlApplicationType applicationType = QmlApplicationTypeCore; -#else -QmlApplicationType applicationType = QmlApplicationTypeGui; -#endif // QT_GUI_LIB -bool quietMode = false; -void printVersion() -{ - printf("qml binary version "); - printf(VERSION_STR); - printf("\nbuilt with Qt version "); - printf(QT_VERSION_STR); - printf("\n"); - exit(0); -} - -void printUsage() -{ - printf("Usage: qml [options] [files] [-- args]\n"); - printf("\n"); - printf("Any unknown argument before '--' will be treated as a QML file to be loaded.\n"); - printf("Any number of QML files can be loaded. They will share the same engine.\n"); - printf("'gui' application type is only available if the QtGui module is available.\n"); - printf("'widget' application type is only available if the QtWidgets module is available.\n"); - printf("\n"); - printf("General Options:\n"); - printf("\t-h, -help..................... Print this usage information and exit.\n"); - printf("\t-v, -version.................. Print the version information and exit.\n"); -#ifdef QT_GUI_LIB -#ifndef QT_WIDGETS_LIB - printf("\t-apptype [core|gui] .......... Select which application class to use. Default is gui.\n"); -#else - printf("\t-apptype [core|gui|widget] ... Select which application class to use. Default is gui.\n"); -#endif // QT_WIDGETS_LIB -#endif // QT_GUI_LIB - printf("\t-quiet ....................... Suppress all output.\n"); - printf("\t-I [path] .................... Prepend the given path to the import paths.\n"); - printf("\t-f [file] .................... Load the given file as a QML file.\n"); - printf("\t-config [file] ............... Load the given file as the configuration file.\n"); - printf("\t-- ........................... Arguments after this one are ignored by the launcher, but may be used within the QML application.\n"); - printf("\tGL options:\n"); - printf("\t-desktop.......................Force use of desktop GL (AA_UseDesktopOpenGL)\n"); - printf("\t-gles..........................Force use of GLES (AA_UseOpenGLES)\n"); - printf("\t-software......................Force use of software rendering (AA_UseOpenGLES)\n"); - printf("\t-scaling.......................Enable High DPI scaling (AA_EnableHighDpiScaling)\n"); - printf("\t-no-scaling....................Disable High DPI scaling (AA_DisableHighDpiScaling)\n"); - printf("\tDebugging options:\n"); - printf("\t-verbose ..................... Print information about what qml is doing, like specific file urls being loaded.\n"); - printf("\t-translation [file] .......... Load the given file as the translations file.\n"); - printf("\t-dummy-data [directory] ...... Load QML files from the given directory as context properties.\n"); - printf("\t-slow-animations ............. Run all animations in slow motion.\n"); - printf("\t-fixed-animations ............ Run animations off animation tick rather than wall time.\n"); - exit(0); -} - -void noFilesGiven() -{ - if (!quietMode) - printf("qml: No files specified. Terminating.\n"); - exit(1); -} - // Called before application initialization, removes arguments it uses void getAppFlags(int &argc, char **argv) { #ifdef QT_GUI_LIB for (int i=0; i<argc; i++) { - if (!strcmp(argv[i], "-apptype")) { // Must be done before application, as it selects application + if (!strcmp(argv[i], "--apptype") || !strcmp(argv[i], "-a") || !strcmp(argv[i], "-apptype")) { applicationType = QmlApplicationTypeUnknown; if (i+1 < argc) { if (!strcmp(argv[i+1], "core")) @@ -380,15 +349,6 @@ void getAppFlags(int &argc, char **argv) applicationType = QmlApplicationTypeWidget; #endif // QT_WIDGETS_LIB } - - if (applicationType == QmlApplicationTypeUnknown) { -#ifndef QT_WIDGETS_LIB - printf("-apptype must be followed by one of the following: core gui\n"); -#else - printf("-apptype must be followed by one of the following: core gui widget\n"); -#endif // QT_WIDGETS_LIB - printUsage(); - } for (int j=i; j<argc-2; j++) argv[j] = argv[j+2]; argc -= 2; @@ -440,24 +400,23 @@ static void loadDummyDataFiles(QQmlEngine &engine, const QString& directory) int main(int argc, char *argv[]) { getAppFlags(argc, argv); - QCoreApplication *app = nullptr; + std::unique_ptr<QCoreApplication> app; switch (applicationType) { - case QmlApplicationTypeCore: - app = new QCoreApplication(argc, argv); - break; #ifdef QT_GUI_LIB case QmlApplicationTypeGui: - app = new LoaderApplication(argc, argv); + app = qt_make_unique<LoaderApplication>(argc, argv); break; #ifdef QT_WIDGETS_LIB case QmlApplicationTypeWidget: - app = new QApplication(argc, argv); - static_cast<QApplication *>(app)->setWindowIcon(QIcon(iconResourcePath)); + app = qt_make_unique<QApplication>(argc, argv); + static_cast<QApplication *>(app.get())->setWindowIcon(QIcon(iconResourcePath)); break; #endif // QT_WIDGETS_LIB #endif // QT_GUI_LIB - default: - Q_ASSERT_X(false, Q_FUNC_INFO, "impossible case"); + case QmlApplicationTypeCore: + Q_FALLTHROUGH(); + default: // QmlApplicationTypeUnknown: not allowed, but we'll exit after checking apptypeOption below + app = qt_make_unique<QCoreApplication>(argc, argv); break; } @@ -475,67 +434,143 @@ int main(int argc, char *argv[]) QString dummyDir; // Handle main arguments - const QStringList argList = app->arguments(); - for (int i = 1; i < argList.count(); i++) { - const QString &arg = argList[i]; - if (arg == QLatin1String("-quiet")) - quietMode = true; - else if (arg == QLatin1String("-v") || arg == QLatin1String("-version")) - printVersion(); - else if (arg == QLatin1String("-h") || arg == QLatin1String("-help")) - printUsage(); - else if (arg == QLatin1String("--")) - break; - else if (arg == QLatin1String("-verbose")) - verboseMode = true; + QCommandLineParser parser; + parser.setSingleDashWordOptionMode(QCommandLineParser::ParseAsLongOptions); + parser.setOptionsAfterPositionalArgumentsMode(QCommandLineParser::ParseAsPositionalArguments); + const QCommandLineOption helpOption = parser.addHelpOption(); + const QCommandLineOption versionOption = parser.addVersionOption(); +#ifdef QT_GUI_LIB + QCommandLineOption apptypeOption(QStringList() << QStringLiteral("a") << QStringLiteral("apptype"), + QCoreApplication::translate("main", "Select which application class to use. Default is gui."), +#ifdef QT_WIDGETS_LIB + QStringLiteral("core|gui|widget")); +#else + QStringLiteral("core|gui")); +#endif // QT_WIDGETS_LIB + parser.addOption(apptypeOption); // Just for the help text... we've already handled this argument above +#endif // QT_GUI_LIB + QCommandLineOption importOption(QStringLiteral("I"), + QCoreApplication::translate("main", "Prepend the given path to the import paths."), QStringLiteral("path")); + parser.addOption(importOption); + QCommandLineOption qmlFileOption(QStringLiteral("f"), + QCoreApplication::translate("main", "Load the given file as a QML file."), QStringLiteral("file")); + parser.addOption(qmlFileOption); + QCommandLineOption configOption(QStringList() << QStringLiteral("c") << QStringLiteral("config"), + QCoreApplication::translate("main", "Load the given built-in configuration or configuration file."), QStringLiteral("file")); + parser.addOption(configOption); + QCommandLineOption listConfOption(QStringList() << QStringLiteral("list-conf"), + QCoreApplication::translate("main", "List the built-in configurations.")); + parser.addOption(listConfOption); + QCommandLineOption translationOption(QStringLiteral("translation"), + QCoreApplication::translate("main", "Load the given file as the translations file."), QStringLiteral("file")); + parser.addOption(translationOption); + QCommandLineOption dummyDataOption(QStringLiteral("dummy-data"), + QCoreApplication::translate("main", "Load QML files from the given directory as context properties."), QStringLiteral("file")); + parser.addOption(dummyDataOption); + // OpenGL options + QCommandLineOption glDesktopOption(QStringLiteral("desktop"), + QCoreApplication::translate("main", "Force use of desktop OpenGL (AA_UseDesktopOpenGL).")); + parser.addOption(glDesktopOption); + QCommandLineOption glEsOption(QStringLiteral("gles"), + QCoreApplication::translate("main", "Force use of GLES (AA_UseOpenGLES).")); + parser.addOption(glEsOption); + QCommandLineOption glSoftwareOption(QStringLiteral("software"), + QCoreApplication::translate("main", "Force use of software rendering (AA_UseSoftwareOpenGL).")); + parser.addOption(glSoftwareOption); + QCommandLineOption scalingOption(QStringLiteral("scaling"), + QCoreApplication::translate("main", "Enable High DPI scaling (AA_EnableHighDpiScaling).")); + parser.addOption(scalingOption); + QCommandLineOption noScalingOption(QStringLiteral("no-scaling"), + QCoreApplication::translate("main", "Disable High DPI scaling (AA_DisableHighDpiScaling).")); + parser.addOption(noScalingOption); + // Debugging and verbosity options + QCommandLineOption quietOption(QStringLiteral("quiet"), + QCoreApplication::translate("main", "Suppress all output.")); + parser.addOption(quietOption); + QCommandLineOption verboseOption(QStringLiteral("verbose"), + QCoreApplication::translate("main", "Print information about what qml is doing, like specific file URLs being loaded.")); + parser.addOption(verboseOption); + QCommandLineOption slowAnimationsOption(QStringLiteral("slow-animations"), + QCoreApplication::translate("main", "Run all animations in slow motion.")); + parser.addOption(slowAnimationsOption); + QCommandLineOption fixedAnimationsOption(QStringLiteral("fixed-animations"), + QCoreApplication::translate("main", "Run animations off animation tick rather than wall time.")); + parser.addOption(fixedAnimationsOption); + QCommandLineOption rhiOption(QStringList() << QStringLiteral("r") << QStringLiteral("rhi"), + QCoreApplication::translate("main", "Use the Qt graphics abstraction (RHI) instead of OpenGL directly. " + "Backend is one of: default, vulkan, metal, d3d11, gl"), + QStringLiteral("backend")); + parser.addOption(rhiOption); + + // Positional arguments + parser.addPositionalArgument("files", + QCoreApplication::translate("main", "Any number of QML files can be loaded. They will share the same engine."), "[files...]"); + parser.addPositionalArgument("args", + QCoreApplication::translate("main", "Arguments after '--' are ignored, but passed through to the application.arguments variable in QML."), "[-- args...]"); + + if (!parser.parse(QCoreApplication::arguments())) { + qWarning() << parser.errorText(); + exit(1); + } + if (parser.isSet(versionOption)) + parser.showVersion(); + if (parser.isSet(helpOption)) + parser.showHelp(); + if (parser.isSet(listConfOption)) + listConfFiles(); + if (applicationType == QmlApplicationTypeUnknown) { +#ifdef QT_WIDGETS_LIB + qWarning() << QCoreApplication::translate("main", "--apptype must be followed by one of the following: core gui widget\n"); +#else + qWarning() << QCoreApplication::translate("main", "--apptype must be followed by one of the following: core gui\n"); +#endif // QT_WIDGETS_LIB + parser.showHelp(); + } + if (parser.isSet(verboseOption)) + verboseMode = true; + if (parser.isSet(quietOption)) { + quietMode = true; + verboseMode = false; + } #if QT_CONFIG(qml_animation) - else if (arg == QLatin1String("-slow-animations")) - QUnifiedTimer::instance()->setSlowModeEnabled(true); - else if (arg == QLatin1String("-fixed-animations")) - QUnifiedTimer::instance()->setConsistentTiming(true); + if (parser.isSet(slowAnimationsOption)) + QUnifiedTimer::instance()->setSlowModeEnabled(true); + if (parser.isSet(fixedAnimationsOption)) + QUnifiedTimer::instance()->setConsistentTiming(true); #endif - else if (arg == QLatin1String("-I")) { - if (i+1 == argList.count()) - continue; // Invalid usage, but just ignore it - e.addImportPath(argList[i+1]); - i++; - } else if (arg == QLatin1String("-f")) { - if (i+1 == argList.count()) - continue; // Invalid usage, but just ignore it - files << argList[i+1]; - i++; - } else if (arg == QLatin1String("-config")){ - if (i+1 == argList.count()) - continue; // Invalid usage, but just ignore it - confFile = argList[i+1]; - i++; - } else if (arg == QLatin1String("-translation")){ - if (i+1 == argList.count()) - continue; // Invalid usage, but just ignore it - translationFile = argList[i+1]; - i++; - } else if (arg == QLatin1String("-dummy-data")){ - if (i+1 == argList.count()) - continue; // Invalid usage, but just ignore it - dummyDir = argList[i+1]; - i++; - } else if (arg == QLatin1String("-gles")) { - QCoreApplication::setAttribute(Qt::AA_UseOpenGLES); - } else if (arg == QLatin1String("-software")) { - QCoreApplication::setAttribute(Qt::AA_UseSoftwareOpenGL); - } else if (arg == QLatin1String("-desktop")) { - QCoreApplication::setAttribute(Qt::AA_UseDesktopOpenGL); - } else if (arg == QLatin1String("-scaling")) { - QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); - } else if (arg == QLatin1String("-no-scaling")) { - QCoreApplication::setAttribute(Qt::AA_DisableHighDpiScaling); - } else { - files << arg; - } + if (parser.isSet(glEsOption)) + QCoreApplication::setAttribute(Qt::AA_UseOpenGLES); + if (parser.isSet(glSoftwareOption)) + QCoreApplication::setAttribute(Qt::AA_UseSoftwareOpenGL); + if (parser.isSet(glDesktopOption)) + QCoreApplication::setAttribute(Qt::AA_UseDesktopOpenGL); + if (parser.isSet(scalingOption)) + QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + if (parser.isSet(noScalingOption)) + QCoreApplication::setAttribute(Qt::AA_DisableHighDpiScaling); + for (const QString &importPath : parser.values(importOption)) + e.addImportPath(importPath); + files << parser.values(qmlFileOption); + if (parser.isSet(configOption)) + confFile = parser.value(configOption); + if (parser.isSet(translationOption)) + translationFile = parser.value(translationOption); + if (parser.isSet(dummyDataOption)) + dummyDir = parser.value(dummyDataOption); + if (parser.isSet(rhiOption)) { + qputenv("QSG_RHI", "1"); + const QString rhiBackend = parser.value(rhiOption); + if (rhiBackend == QLatin1String("default")) + qunsetenv("QSG_RHI_BACKEND"); + else + qputenv("QSG_RHI_BACKEND", rhiBackend.toLatin1()); + } + for (QString posArg : parser.positionalArguments()) { + if (posArg == QLatin1String("--")) + break; + else + files << posArg; } - - if (quietMode && verboseMode) - verboseMode = false; #if QT_CONFIG(translation) // Need to be installed before QQmlApplicationEngine's automatic translation loading @@ -557,13 +592,15 @@ int main(int argc, char *argv[]) printf("qml: Translation file specified, but Qt built without translation support.\n"); #endif - if (quietMode) + if (quietMode) { qInstallMessageHandler(quietMessageHandler); + QLoggingCategory::setFilterRules(QStringLiteral("*=false")); + } if (files.count() <= 0) { #if defined(Q_OS_DARWIN) if (applicationType == QmlApplicationTypeGui) - exitTimerId = static_cast<LoaderApplication *>(app)->startTimer(FILE_OPEN_EVENT_WAIT_TIME); + exitTimerId = static_cast<LoaderApplication *>(app.get())->startTimer(FILE_OPEN_EVENT_WAIT_TIME); else #endif noFilesGiven(); diff --git a/tools/qml/qml.qrc b/tools/qml/qml.qrc index e4be1793d4..69aa4a5756 100644 --- a/tools/qml/qml.qrc +++ b/tools/qml/qml.qrc @@ -1,7 +1,9 @@ <RCC> <qresource prefix="qt-project.org/QmlRuntime"> - <file>conf/configuration.qml</file> - <file>conf/qtquick.qml</file> + <file>conf/default.qml</file> + <file>conf/resizeToItem.qml</file> + <file>conf/content/resizeItemToWindow.qml</file> + <file>conf/content/resizeWindowToItem.qml</file> <file>resources/qml-64.png</file> </qresource> </RCC> diff --git a/tools/qmlcachegen/generateloader.cpp b/tools/qmlcachegen/generateloader.cpp index 4fbbb27afb..1c8a5a016a 100644 --- a/tools/qmlcachegen/generateloader.cpp +++ b/tools/qmlcachegen/generateloader.cpp @@ -75,7 +75,7 @@ QString mangledIdentifier(const QString &str) || (c >= QLatin1Char('a') && c <= QLatin1Char('z')) || (c >= QLatin1Char('A') && c <= QLatin1Char('Z')) || c == QLatin1Char('_')) { - mangled += c; + mangled += QChar(c); } else { mangled += QLatin1String("_0x") + QString::number(c, 16) + QLatin1Char('_'); } @@ -434,7 +434,11 @@ bool generateLoader(const QStringList &compiledFiles, const QStringList &sortedR } } +#if QT_CONFIG(temporaryfile) QSaveFile f(outputFileName); +#else + QFile f(outputFileName); +#endif if (!f.open(QIODevice::WriteOnly | QIODevice::Truncate)) { *errorString = f.errorString(); return false; @@ -445,10 +449,12 @@ bool generateLoader(const QStringList &compiledFiles, const QStringList &sortedR return false; } +#if QT_CONFIG(temporaryfile) if (!f.commit()) { *errorString = f.errorString(); return false; } +#endif return true; } diff --git a/tools/qmlcachegen/qmlcachegen.cpp b/tools/qmlcachegen/qmlcachegen.cpp index df7468eaef..ac9cf039d3 100644 --- a/tools/qmlcachegen/qmlcachegen.cpp +++ b/tools/qmlcachegen/qmlcachegen.cpp @@ -45,6 +45,8 @@ #include <algorithm> +using namespace QQmlJS; + int filterResourceFile(const QString &input, const QString &output); bool generateLoader(const QStringList &compiledFiles, const QStringList &retainedFiles, const QString &output, const QStringList &resourceFileMappings, @@ -65,6 +67,7 @@ struct Error void print(); Error augment(const QString &contextErrorMessage) const; void appendDiagnostics(const QString &inputFileName, const QList<QQmlJS::DiagnosticMessage> &diagnostics); + void appendDiagnostic(const QString &inputFileName, const DiagnosticMessage &diagnostic); }; void Error::print() @@ -82,9 +85,9 @@ Error Error::augment(const QString &contextErrorMessage) const QString diagnosticErrorMessage(const QString &fileName, const QQmlJS::DiagnosticMessage &m) { QString message; - message = fileName + QLatin1Char(':') + QString::number(m.loc.startLine) + QLatin1Char(':'); - if (m.loc.startColumn > 0) - message += QString::number(m.loc.startColumn) + QLatin1Char(':'); + message = fileName + QLatin1Char(':') + QString::number(m.line) + QLatin1Char(':'); + if (m.column > 0) + message += QString::number(m.column) + QLatin1Char(':'); if (m.isError()) message += QLatin1String(" error: "); @@ -94,13 +97,17 @@ QString diagnosticErrorMessage(const QString &fileName, const QQmlJS::Diagnostic return message; } +void Error::appendDiagnostic(const QString &inputFileName, const DiagnosticMessage &diagnostic) +{ + if (!message.isEmpty()) + message += QLatin1Char('\n'); + message += diagnosticErrorMessage(inputFileName, diagnostic); +} + void Error::appendDiagnostics(const QString &inputFileName, const QList<DiagnosticMessage> &diagnostics) { - for (const QQmlJS::DiagnosticMessage &parseError: diagnostics) { - if (!message.isEmpty()) - message += QLatin1Char('\n'); - message += diagnosticErrorMessage(inputFileName, parseError); - } + for (const QQmlJS::DiagnosticMessage &diagnostic: diagnostics) + appendDiagnostic(inputFileName, diagnostic); } // Ensure that ListElement objects keep all property assignments in their string form @@ -169,7 +176,7 @@ static bool checkArgumentsObjectUseInSignalHandlers(const QmlIR::Document &doc, return true; } -using SaveFunction = std::function<bool (const QQmlRefPointer<QV4::CompiledData::CompilationUnit> &, QString *)>; +using SaveFunction = std::function<bool(const QV4::CompiledData::SaveableUnitPointer &, QString *)>; static bool compileQmlFile(const QString &inputFileName, SaveFunction saveFunction, Error *error) { @@ -200,10 +207,7 @@ static bool compileQmlFile(const QString &inputFileName, SaveFunction saveFuncti annotateListElements(&irDocument); { - QmlIR::JSCodeGen v4CodeGen(irDocument.code, - &irDocument.jsGenerator, &irDocument.jsModule, - &irDocument.jsParserEngine, irDocument.program, - &irDocument.jsGenerator.stringTable, illegalNames); + QmlIR::JSCodeGen v4CodeGen(&irDocument, illegalNames); for (QmlIR::Object *object: qAsConst(irDocument.objects)) { if (object->functionsAndExpressions->count == 0) continue; @@ -211,9 +215,8 @@ static bool compileQmlFile(const QString &inputFileName, SaveFunction saveFuncti for (QmlIR::CompiledFunctionOrExpression *foe = object->functionsAndExpressions->first; foe; foe = foe->next) functionsToCompile << *foe; const QVector<int> runtimeFunctionIndices = v4CodeGen.generateJSCodeForFunctionsAndBindings(functionsToCompile); - QList<QQmlJS::DiagnosticMessage> jsErrors = v4CodeGen.errors(); - if (!jsErrors.isEmpty()) { - error->appendDiagnostics(inputFileName, jsErrors); + if (v4CodeGen.hasError()) { + error->appendDiagnostic(inputFileName, v4CodeGen.error()); return false; } @@ -229,11 +232,13 @@ static bool compileQmlFile(const QString &inputFileName, SaveFunction saveFuncti QmlIR::QmlUnitGenerator generator; irDocument.javaScriptCompilationUnit = v4CodeGen.generateCompilationUnit(/*generate unit*/false); generator.generate(irDocument); - QV4::CompiledData::Unit *unit = const_cast<QV4::CompiledData::Unit*>(irDocument.javaScriptCompilationUnit->data); - unit->flags |= QV4::CompiledData::Unit::StaticData; - unit->flags |= QV4::CompiledData::Unit::PendingTypeCompilation; - if (!saveFunction(irDocument.javaScriptCompilationUnit, &error->message)) + const quint32 saveFlags + = QV4::CompiledData::Unit::StaticData + | QV4::CompiledData::Unit::PendingTypeCompilation; + QV4::CompiledData::SaveableUnitPointer saveable(irDocument.javaScriptCompilationUnit.data, + saveFlags); + if (!saveFunction(saveable, &error->message)) return false; } return true; @@ -241,7 +246,7 @@ static bool compileQmlFile(const QString &inputFileName, SaveFunction saveFuncti static bool compileJSFile(const QString &inputFileName, const QString &inputFileUrl, SaveFunction saveFunction, Error *error) { - QQmlRefPointer<QV4::CompiledData::CompilationUnit> unit; + QV4::CompiledData::CompilationUnit unit; QString sourceCode; { @@ -262,9 +267,10 @@ static bool compileJSFile(const QString &inputFileName, const QString &inputFile QList<QQmlJS::DiagnosticMessage> diagnostics; // Precompiled files are relocatable and the final location will be set when loading. QString url; - unit = QV4::ExecutionEngine::compileModule(/*debugMode*/false, url, sourceCode, QDateTime(), &diagnostics); + unit = QV4::Compiler::Codegen::compileModule(/*debugMode*/false, url, sourceCode, + QDateTime(), &diagnostics); error->appendDiagnostics(inputFileName, diagnostics); - if (!unit) + if (!unit.unitData()) return false; } else { QmlIR::Document irDocument(/*debugMode*/false); @@ -302,14 +308,11 @@ static bool compileJSFile(const QString &inputFileName, const QString &inputFile } { - QmlIR::JSCodeGen v4CodeGen(irDocument.code, &irDocument.jsGenerator, - &irDocument.jsModule, &irDocument.jsParserEngine, - irDocument.program, &irDocument.jsGenerator.stringTable, illegalNames); + QmlIR::JSCodeGen v4CodeGen(&irDocument, illegalNames); v4CodeGen.generateFromProgram(inputFileName, inputFileUrl, sourceCode, program, &irDocument.jsModule, QV4::Compiler::ContextType::ScriptImportedByQML); - QList<QQmlJS::DiagnosticMessage> jsErrors = v4CodeGen.errors(); - if (!jsErrors.isEmpty()) { - error->appendDiagnostics(inputFileName, jsErrors); + if (v4CodeGen.hasError()) { + error->appendDiagnostic(inputFileName, v4CodeGen.error()); return false; } @@ -320,19 +323,22 @@ static bool compileJSFile(const QString &inputFileName, const QString &inputFile irDocument.javaScriptCompilationUnit = v4CodeGen.generateCompilationUnit(/*generate unit*/false); QmlIR::QmlUnitGenerator generator; generator.generate(irDocument); - QV4::CompiledData::Unit *unitData = const_cast<QV4::CompiledData::Unit*>(irDocument.javaScriptCompilationUnit->data); - unitData->flags |= QV4::CompiledData::Unit::StaticData; - unit = irDocument.javaScriptCompilationUnit; + unit = std::move(irDocument.javaScriptCompilationUnit); } } - return saveFunction(unit, &error->message); + return saveFunction(QV4::CompiledData::SaveableUnitPointer(unit.data), &error->message); } static bool saveUnitAsCpp(const QString &inputFileName, const QString &outputFileName, - const QQmlRefPointer<QV4::CompiledData::CompilationUnit> &unit, QString *errorString) + const QV4::CompiledData::SaveableUnitPointer &unit, + QString *errorString) { +#if QT_CONFIG(temporaryfile) QSaveFile f(outputFileName); +#else + QFile f(outputFileName); +#endif if (!f.open(QIODevice::WriteOnly | QIODevice::Truncate)) { *errorString = f.errorString(); return false; @@ -364,43 +370,38 @@ static bool saveUnitAsCpp(const QString &inputFileName, const QString &outputFil if (!writeStr(QByteArrayLiteral(" {\nextern const unsigned char qmlData alignas(16) [] = {\n"))) return false; - QByteArray hexifiedData; - { - QByteArray modifiedUnit; - modifiedUnit.resize(unit->data->unitSize); - memcpy(modifiedUnit.data(), unit->data, unit->data->unitSize); - const char *dataPtr = modifiedUnit.data(); - QV4::CompiledData::Unit *unitPtr; - memcpy(&unitPtr, &dataPtr, sizeof(unitPtr)); - unitPtr->flags |= QV4::CompiledData::Unit::StaticData; - - QTextStream stream(&hexifiedData); - const uchar *begin = reinterpret_cast<const uchar *>(modifiedUnit.constData()); - const uchar *end = begin + unit->data->unitSize; - stream << hex; - int col = 0; - for (const uchar *data = begin; data < end; ++data, ++col) { - if (data > begin) - stream << ','; - if (col % 8 == 0) { - stream << '\n'; - col = 0; + unit.saveToDisk<uchar>([&writeStr](const uchar *begin, quint32 size) { + QByteArray hexifiedData; + { + QTextStream stream(&hexifiedData); + const uchar *end = begin + size; + stream << hex; + int col = 0; + for (const uchar *data = begin; data < end; ++data, ++col) { + if (data > begin) + stream << ','; + if (col % 8 == 0) { + stream << '\n'; + col = 0; + } + stream << "0x" << *data; } - stream << "0x" << *data; + stream << '\n'; } - stream << '\n'; - }; + return writeStr(hexifiedData); + }); + - if (!writeStr(hexifiedData)) - return false; if (!writeStr("};\n}\n}\n")) return false; +#if QT_CONFIG(temporaryfile) if (!f.commit()) { *errorString = f.errorString(); return false; } +#endif return true; } @@ -524,13 +525,20 @@ int main(int argc, char **argv) inputFileUrl = QStringLiteral("qrc://") + inputResourcePath; - saveFunction = [inputResourcePath, outputFileName](const QQmlRefPointer<QV4::CompiledData::CompilationUnit> &unit, QString *errorString) { + saveFunction = [inputResourcePath, outputFileName]( + const QV4::CompiledData::SaveableUnitPointer &unit, + QString *errorString) { return saveUnitAsCpp(inputResourcePath, outputFileName, unit, errorString); }; } else { - saveFunction = [outputFileName](const QQmlRefPointer<QV4::CompiledData::CompilationUnit> &unit, QString *errorString) { - return unit->saveToDisk(outputFileName, errorString); + saveFunction = [outputFileName](const QV4::CompiledData::SaveableUnitPointer &unit, + QString *errorString) { + return unit.saveToDisk<char>( + [&outputFileName, errorString](const char *data, quint32 size) { + return QV4::CompiledData::SaveableUnitPointer::writeDataToFile( + outputFileName, data, size, errorString); + }); }; } diff --git a/tools/qmlcachegen/qmlcachegen.pro b/tools/qmlcachegen/qmlcachegen.pro index bee0b9a37e..ec65cdb5e6 100644 --- a/tools/qmlcachegen/qmlcachegen.pro +++ b/tools/qmlcachegen/qmlcachegen.pro @@ -5,8 +5,10 @@ DEFINES += QT_NO_CAST_TO_ASCII QT_NO_CAST_FROM_ASCII SOURCES = qmlcachegen.cpp \ resourcefilter.cpp \ - generateloader.cpp \ - resourcefilemapper.cpp + generateloader.cpp + +include(../shared/shared.pri) + TARGET = qmlcachegen build_integration.files = qmlcache.prf qtquickcompiler.prf @@ -38,5 +40,3 @@ QMAKE_TARGET_DESCRIPTION = QML Cache Generator load(qt_tool) -HEADERS += \ - resourcefilemapper.h diff --git a/tools/qmlimportscanner/Qt5QmlImportScannerConfig.cmake.in b/tools/qmlimportscanner/Qt5QmlImportScannerConfig.cmake.in new file mode 100644 index 0000000000..6cdfaf8f6f --- /dev/null +++ b/tools/qmlimportscanner/Qt5QmlImportScannerConfig.cmake.in @@ -0,0 +1,184 @@ +include(CMakeParseArguments) + +function(QT5_IMPORT_QML_PLUGINS target) +!!IF !isEmpty(CMAKE_STATIC_TYPE) + set(options) + set(oneValueArgs \"PATH_TO_SCAN\") + set(multiValueArgs) + + cmake_parse_arguments(arg \"${options}\" \"${oneValueArgs}\" \"${multiValueArgs}\" ${ARGN}) + if(NOT arg_PATH_TO_SCAN) + set(arg_PATH_TO_SCAN \"${CMAKE_CURRENT_SOURCE_DIR}\") + endif() + + # Find location of qmlimportscanner. + find_package(Qt5 COMPONENTS Core) +!!IF isEmpty(CMAKE_BIN_DIR_IS_ABSOLUTE) + set(tool_path + \"${_qt5Core_install_prefix}/$${CMAKE_BIN_DIR}qmlimportscanner$$CMAKE_BIN_SUFFIX\") +!!ELSE + set(tool_path \"$${CMAKE_BIN_DIR}qmlimportscanner$$CMAKE_BIN_SUFFIX\") +!!ENDIF + if(NOT EXISTS \"${tool_path}\" ) + message(FATAL_ERROR \"The package \\\"Qt5QmlImportScannerConfig\\\" references the file + \\\"${tool_path}\\\" +but this file does not exist. Possible reasons include: +* The file was deleted, renamed, or moved to another location. +* An install or uninstall procedure did not complete successfully. +* The installation package was faulty. +\") + endif() + + # Find location of qml dir. +!!IF isEmpty(CMAKE_QML_DIR_IS_ABSOLUTE) + set(qml_path \"${_qt5Core_install_prefix}/$${CMAKE_QML_DIR}\") +!!ELSE + set(qml_path \"$${CMAKE_QML_DIR}\") +!!ENDIF + + # Small macro to avoid duplicating code in two different loops. + macro(_qt5_QmlImportScanner_parse_entry) + set(entry_name \"qml_import_scanner_import_${idx}\") + cmake_parse_arguments(\"entry\" + \"\" + \"CLASSNAME;NAME;PATH;PLUGIN;RELATIVEPATH;TYPE;VERSION;\" \"\" + ${${entry_name}}) + endmacro() + + # Macro used to populate the dependency link flags for a certain configuriation (debug vs + # release) of a plugin. + macro(_qt5_link_to_QmlImportScanner_library_dependencies Plugin Configuration PluginLocation + IsDebugAndRelease) + + set_property(TARGET \"${Plugin}\" APPEND PROPERTY IMPORTED_CONFIGURATIONS ${Configuration}) + set(_imported_location \"${PluginLocation}\") + _qt5_Core_check_file_exists(\"${_imported_location}\") + set_target_properties(\"${Plugin}\" PROPERTIES + \"IMPORTED_LOCATION_${Configuration}\" \"${_imported_location}\" + ) + + set(_static_deps + ${_Qt5${entry_PLUGIN}_STATIC_${Configuration}_LIB_DEPENDENCIES} + ) + + if(NOT "${IsDebugAndRelease}") + set(_genex_condition \"1\") + else() + if("${Configuration}" STREQUAL "DEBUG") + set(_genex_condition \"$<CONFIG:Debug>\") + else() + set(_genex_condition \"$<NOT:$<CONFIG:Debug>>\") + endif() + endif() + if(_static_deps) + set(_static_deps_genex \"$<${_genex_condition}:${_static_deps}>\") + target_link_libraries(${imported_target} INTERFACE \"${_static_deps_genex}\") + endif() + + set(_static_link_flags \"${_Qt5${entry_PLUGIN}_STATIC_${Configuration}_LINK_FLAGS}\") + if(NOT CMAKE_VERSION VERSION_LESS \"3.13\" AND _static_link_flags) + set(_static_link_flags_genex \"$<${_genex_condition}:${_static_link_flags}>\") + target_link_options(${imported_target} INTERFACE \"${_static_link_flags_genex}\") + endif() + endmacro() + + # Run qmlimportscanner and include the generated cmake file. + set(qml_imports_file_path + \"${CMAKE_CURRENT_BINARY_DIR}/Qt5_QmlPlugins_Imports_${target}.cmake\") + + message(STATUS \"Running qmlimportscanner to find used QML plugins. \") + execute_process(COMMAND + \"${tool_path}\" \"${arg_PATH_TO_SCAN}\" -importPath \"${qml_path}\" + -cmake-output + OUTPUT_FILE \"${qml_imports_file_path}\") + + include(\"${qml_imports_file_path}\" OPTIONAL RESULT_VARIABLE qml_imports_file_path_found) + if(NOT qml_imports_file_path_found) + message(FATAL_ERROR \"Could not find ${qml_imports_file_path} which was supposed to be generated by qmlimportscanner.\") + endif() + + # Parse the generate cmake file. + # It is possible for the scanner to find no usage of QML, in which case the import count is 0. + if(qml_import_scanner_imports_count) + set(added_plugins \"\") + foreach(idx RANGE \"${qml_import_scanner_imports_count}\") + _qt5_QmlImportScanner_parse_entry() + if(entry_PATH AND entry_PLUGIN) + # Sometimes a plugin appears multiple times with different versions. + # Make sure to process it only once. + list(FIND added_plugins \"${entry_PLUGIN}\" _index) + if(NOT _index EQUAL -1) + continue() + endif() + list(APPEND added_plugins \"${entry_PLUGIN}\") + + # Add an imported target that will contain the link libraries and link options read + # from one plugin prl file. This target will point to the actual plugin and contain + # static dependency libraries and link flags. + # By creating a target for each qml plugin, CMake will take care of link flag + # deduplication. + set(imported_target \"${target}_QmlImport_${entry_PLUGIN}\") + add_library(\"${imported_target}\" MODULE IMPORTED) + target_link_libraries(\"${target}\" PRIVATE \"${imported_target}\") + + # Read static library dependencies from the plugin .prl file. + # And then set the link flags to the library dependencies extracted from the .prl + # file. +!!IF !isEmpty(CMAKE_RELEASE_TYPE) + _qt5_Core_process_prl_file( + \"${entry_PATH}/$$QMAKE_PREFIX_STATICLIB${entry_PLUGIN}$${CMAKE_QML_PLUGIN_SUFFIX_RELEASE}.prl\" RELEASE + _Qt5${entry_PLUGIN}_STATIC_RELEASE_LIB_DEPENDENCIES + _Qt5${entry_PLUGIN}_STATIC_RELEASE_LINK_FLAGS + ) + _qt5_link_to_QmlImportScanner_library_dependencies( + \"${imported_target}\" + RELEASE + \"${entry_PATH}/$$QMAKE_PREFIX_STATICLIB${entry_PLUGIN}$${CMAKE_QML_PLUGIN_SUFFIX_RELEASE}.$$QMAKE_EXTENSION_STATICLIB\" + $${CMAKE_DEBUG_AND_RELEASE}) +!!ENDIF + +!!IF !isEmpty(CMAKE_DEBUG_TYPE) + _qt5_Core_process_prl_file( + \"${entry_PATH}/$$QMAKE_PREFIX_STATICLIB${entry_PLUGIN}$${CMAKE_QML_PLUGIN_SUFFIX_DEBUG}.prl\" DEBUG + _Qt5${entry_PLUGIN}_STATIC_DEBUG_LIB_DEPENDENCIES + _Qt5${entry_PLUGIN}_STATIC_DEBUG_LINK_FLAGS + ) + _qt5_link_to_QmlImportScanner_library_dependencies( + \"${imported_target}\" + DEBUG + \"${entry_PATH}/$$QMAKE_PREFIX_STATICLIB${entry_PLUGIN}$${CMAKE_QML_PLUGIN_SUFFIX_DEBUG}.$$QMAKE_EXTENSION_STATICLIB\" + $${CMAKE_DEBUG_AND_RELEASE}) +!!ENDIF + endif() + endforeach() + + # Generate content for plugin initialization cpp file. + set(added_imports \"\") + set(qt5_qml_import_cpp_file_content \"\") + foreach(idx RANGE \"${qml_import_scanner_imports_count}\") + _qt5_QmlImportScanner_parse_entry() + if(entry_PLUGIN) + if(entry_CLASSNAME) + list(FIND added_imports \"${entry_PLUGIN}\" _index) + if(_index EQUAL -1) + string(APPEND qt5_qml_import_cpp_file_content + \"Q_IMPORT_PLUGIN(${entry_CLASSNAME})\n\") + list(APPEND added_imports \"${entry_PLUGIN}\") + endif() + else() + message(FATAL_ERROR + \"Plugin ${entry_PLUGIN} is missing a classname entry, please add one to the qmldir file.\") + endif() + endif() + endforeach() + + # Write to the generated file, and include it as a source for the given target. + set(generated_import_cpp_path + \"${CMAKE_CURRENT_BINARY_DIR}/Qt5_QmlPlugins_Imports_${target}.cpp\") + configure_file(\"${Qt5QmlImportScanner_DIR}/Qt5QmlImportScannerTemplate.cpp.in\" + \"${generated_import_cpp_path}\" + @ONLY) + target_sources(${target} PRIVATE \"${generated_import_cpp_path}\") + endif() +!!ENDIF // !isEmpty(CMAKE_STATIC_TYPE) +endfunction() diff --git a/tools/qmlimportscanner/Qt5QmlImportScannerTemplate.cpp.in b/tools/qmlimportscanner/Qt5QmlImportScannerTemplate.cpp.in new file mode 100644 index 0000000000..4ed747a555 --- /dev/null +++ b/tools/qmlimportscanner/Qt5QmlImportScannerTemplate.cpp.in @@ -0,0 +1,5 @@ +// This file is autogenerated by CMake. It imports static plugin classes for +// static plugins used by QML imports. +#include <QtPlugin> + +@qt5_qml_import_cpp_file_content@ diff --git a/tools/qmlimportscanner/main.cpp b/tools/qmlimportscanner/main.cpp index 616de9e80d..c37910bdaf 100644 --- a/tools/qmlimportscanner/main.cpp +++ b/tools/qmlimportscanner/main.cpp @@ -30,8 +30,9 @@ #include <private/qqmljsparser_p.h> #include <private/qqmljsast_p.h> #include <private/qv4codegen_p.h> -#include <private/qv4value_p.h> +#include <private/qv4staticvalue_p.h> #include <private/qqmlirbuilder_p.h> +#include <private/qqmljsdiagnosticmessage_p.h> #include <QtCore/QCoreApplication> #include <QtCore/QDir> @@ -48,6 +49,8 @@ #include <QtCore/QJsonDocument> #include <QtCore/QLibraryInfo> +#include <resourcefilemapper.h> + #include <iostream> #include <algorithm> @@ -79,13 +82,14 @@ void printUsage(const QString &appNameIn) #endif std::wcerr << "Usage: " << appName << " -rootPath path/to/app/qml/directory -importPath path/to/qt/qml/directory\n" - " " << appName << " -qmlFiles file1 file2 -importPath path/to/qt/qml/directory\n\n" + " " << appName << " -qmlFiles file1 file2 -importPath path/to/qt/qml/directory\n" + " " << appName << " -qrcFiles file1.qrc file2.qrc -importPath path/to/qt/qml/directory\n\n" "Example: " << appName << " -rootPath . -importPath " << QDir::toNativeSeparators(qmlPath).toStdWString() << '\n'; } -QVariantList findImportsInAst(QQmlJS::AST::UiHeaderItemList *headerItemList, const QString &code, const QString &path) +QVariantList findImportsInAst(QQmlJS::AST::UiHeaderItemList *headerItemList, const QString &path) { QVariantList imports; @@ -119,7 +123,8 @@ QVariantList findImportsInAst(QQmlJS::AST::UiHeaderItemList *headerItemList, con if (!name.isEmpty()) import[nameLiteral()] = name; import[typeLiteral()] = moduleLiteral(); - import[versionLiteral()] = code.mid(importNode->versionToken.offset, importNode->versionToken.length); + auto versionString = importNode->version ? QString::number(importNode->version->majorVersion) + QLatin1Char('.') + QString::number(importNode->version->minorVersion) : QString(); + import[versionLiteral()] = versionString; } imports.append(import); @@ -272,11 +277,11 @@ QVariantList findQmlImportsInQmlCode(const QString &filePath, const QString &cod const auto diagnosticMessages = parser.diagnosticMessages(); for (const QQmlJS::DiagnosticMessage &m : diagnosticMessages) { std::cerr << QDir::toNativeSeparators(filePath).toStdString() << ':' - << m.loc.startLine << ':' << m.message.toStdString() << std::endl; + << m.line << ':' << m.message.toStdString() << std::endl; } return QVariantList(); } - return findImportsInAst(parser.ast()->headers, code, filePath); + return findImportsInAst(parser.ast()->headers, filePath); } // Scan a single qml file for import statements @@ -494,6 +499,36 @@ QVariantList findQmlImportsRecursively(const QStringList &qmlDirs, const QString return ret; } + +QString generateCmakeIncludeFileContent(const QVariantList &importList) { + // The function assumes that "list" is a QVariantList with 0 or more QVariantMaps, where + // each map contains QString -> QVariant<QString> mappings. This matches with the structure + // that qmake parses for static qml plugin auto imporitng. + // So: [ {"a": "a","b": "b"}, {"c": "c"} ] + QString content; + QTextStream s(&content); + int importsCount = 0; + for (const QVariant &importVariant: importList) { + if (static_cast<QMetaType::Type>(importVariant.type()) == QMetaType::QVariantMap) { + s << QStringLiteral("set(qml_import_scanner_import_") << importsCount + << QStringLiteral(" \""); + + const QMap<QString, QVariant> &importDict = importVariant.toMap(); + for (auto it = importDict.cbegin(); it != importDict.cend(); ++it) { + s << it.key().toUpper() << QLatin1Char(';') + << it.value().toString() << QLatin1Char(';'); + } + s << QStringLiteral("\")\n"); + ++importsCount; + } + } + if (importsCount >= 0) { + content.prepend(QString(QStringLiteral("set(qml_import_scanner_imports_count %1)\n")) + .arg(importsCount)); + } + return content; +} + } // namespace int main(int argc, char *argv[]) @@ -510,6 +545,8 @@ int main(int argc, char *argv[]) QStringList qmlRootPaths; QStringList scanFiles; QStringList qmlImportPaths; + QStringList qrcFiles; + bool generateCmakeContent = false; int i = 1; while (i < args.count()) { @@ -534,6 +571,10 @@ int main(int argc, char *argv[]) if (i >= args.count()) std::cerr << "-importPath requires an argument\n"; argReceiver = &qmlImportPaths; + } else if (arg == QLatin1String("-cmake-output")) { + generateCmakeContent = true; + } else if (arg == QLatin1String("-qrcFiles")) { + argReceiver = &qrcFiles; } else { std::cerr << qPrintable(appName) << ": Invalid argument: \"" << qPrintable(arg) << "\"\n"; @@ -555,13 +596,23 @@ int main(int argc, char *argv[]) } } + if (!qrcFiles.isEmpty()) + scanFiles << ResourceFileMapper(qrcFiles).qmlCompilerFiles(ResourceFileMapper::FileOutput::AbsoluteFilePath); + g_qmlImportPaths = qmlImportPaths; // Find the imports! QVariantList imports = findQmlImportsRecursively(qmlRootPaths, scanFiles); - // Convert to JSON - QByteArray json = QJsonDocument(QJsonArray::fromVariantList(imports)).toJson(); - std::cout << json.constData() << std::endl; + QByteArray content; + if (generateCmakeContent) { + // Convert to CMake code + content = generateCmakeIncludeFileContent(imports).toUtf8(); + } else { + // Convert to JSON + content = QJsonDocument(QJsonArray::fromVariantList(imports)).toJson(); + } + + std::cout << content.constData() << std::endl; return 0; } diff --git a/tools/qmlimportscanner/qmlimportscanner.pro b/tools/qmlimportscanner/qmlimportscanner.pro index 0b3a03abf3..d69f1a3b0b 100644 --- a/tools/qmlimportscanner/qmlimportscanner.pro +++ b/tools/qmlimportscanner/qmlimportscanner.pro @@ -4,6 +4,52 @@ QT = core qmldevtools-private DEFINES += QT_NO_CAST_TO_ASCII QT_NO_CAST_FROM_ASCII SOURCES += main.cpp +include(../shared/shared.pri) + +load(cmake_functions) + +CMAKE_BIN_DIR = $$cmakeRelativePath($$[QT_HOST_BINS], $$[QT_INSTALL_PREFIX]) +contains(CMAKE_BIN_DIR, "^\\.\\./.*") { + CMAKE_BIN_DIR = $$[QT_HOST_BINS]/ + CMAKE_BIN_DIR_IS_ABSOLUTE = True +} + +CMAKE_QML_DIR = $$cmakeRelativePath($$[QT_INSTALL_QML/get], $$[QT_INSTALL_PREFIX]) +contains(CMAKE_QML_DIR, "^\\.\\./.*") { + CMAKE_QML_DIR = $$[QT_INSTALL_QML/get]/ + CMAKE_QML_DIR_IS_ABSOLUTE = True +} +load(qt_build_paths) + +static|staticlib:CMAKE_STATIC_TYPE = true + +# Compute the platform target suffix. +CMAKE_QML_PLUGIN_SUFFIX_RELEASE = +win32: CMAKE_QML_PLUGIN_SUFFIX_DEBUG = d +else:darwin: CMAKE_QML_PLUGIN_SUFFIX_DEBUG = _debug +else: CMAKE_QML_PLUGIN_SUFFIX_DEBUG = + +# Find out which configurations should be handled in the generated Config.cmake file. +CMAKE_DEBUG_TYPE = +CMAKE_RELEASE_TYPE = +if(qtConfig(debug_and_release)|contains(QT_CONFIG, debug, debug|release)): CMAKE_DEBUG_TYPE = debug +if(qtConfig(debug_and_release)|contains(QT_CONFIG, release, debug|release)): CMAKE_RELEASE_TYPE = release + +qtConfig(debug_and_release) { + CMAKE_DEBUG_AND_RELEASE = TRUE +} else { + CMAKE_DEBUG_AND_RELEASE = FALSE +} + +equals(QMAKE_HOST.os, Windows): CMAKE_BIN_SUFFIX = ".exe" +cmake_config_file.input = $$PWD/Qt5QmlImportScannerConfig.cmake.in +cmake_config_file.output = $$MODULE_BASE_OUTDIR/lib/cmake/Qt5QmlImportScanner/Qt5QmlImportScannerConfig.cmake +QMAKE_SUBSTITUTES += cmake_config_file + +cmake_build_integration.files = $$cmake_config_file.output $$PWD/Qt5QmlImportScannerTemplate.cpp.in +cmake_build_integration.path = $$[QT_INSTALL_LIBS]/cmake/Qt5QmlImportScanner +prefix_build: INSTALLS += cmake_build_integration +else: COPIES += cmake_build_integration QMAKE_TARGET_DESCRIPTION = QML Import Scanner diff --git a/tools/qmljs/qmljs.cpp b/tools/qmljs/qmljs.cpp index 4b581fff05..ba5e5f553c 100644 --- a/tools/qmljs/qmljs.cpp +++ b/tools/qmljs/qmljs.cpp @@ -137,7 +137,8 @@ int main(int argc, char *argv[]) } QScopedPointer<QV4::Script> script; if (cache && QFile::exists(fn + QLatin1Char('c'))) { - QQmlRefPointer<QV4::CompiledData::CompilationUnit> unit = QV4::Compiler::Codegen::createUnitForLoading(); + QQmlRefPointer<QV4::ExecutableCompilationUnit> unit + = QV4::ExecutableCompilationUnit::create(); QString error; if (unit->loadFromDisk(QUrl::fromLocalFile(fn), QFileInfo(fn).lastModified(), &error)) { script.reset(new QV4::Script(&vm, nullptr, unit)); diff --git a/tools/qmllint/componentversion.cpp b/tools/qmllint/componentversion.cpp new file mode 100644 index 0000000000..3dc4ac37d0 --- /dev/null +++ b/tools/qmllint/componentversion.cpp @@ -0,0 +1,123 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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 "componentversion.h" + +#include <QString> +#include <QCryptographicHash> + +#include <limits> + +using namespace LanguageUtils; + +const int ComponentVersion::NoVersion = -1; +const int ComponentVersion::MaxVersion = std::numeric_limits<int>::max(); + +ComponentVersion::ComponentVersion() + : _major(NoVersion), _minor(NoVersion) +{ +} + +ComponentVersion::ComponentVersion(int major, int minor) + : _major(major), _minor(minor) +{ +} + +ComponentVersion::ComponentVersion(const QString &versionString) + : _major(NoVersion), _minor(NoVersion) +{ + int dotIdx = versionString.indexOf(QLatin1Char('.')); + if (dotIdx == -1) + return; + bool ok = false; + int maybeMajor = versionString.leftRef(dotIdx).toInt(&ok); + if (!ok) + return; + int maybeMinor = versionString.midRef(dotIdx + 1).toInt(&ok); + if (!ok) + return; + _major = maybeMajor; + _minor = maybeMinor; +} + +ComponentVersion::~ComponentVersion() +{ +} + +bool ComponentVersion::isValid() const +{ + return _major >= 0 && _minor >= 0; +} + +QString ComponentVersion::toString() const +{ + return QString::fromLatin1("%1.%2").arg(QString::number(_major), + QString::number(_minor)); +} + +void ComponentVersion::addToHash(QCryptographicHash &hash) const +{ + hash.addData(reinterpret_cast<const char *>(&_major), sizeof(_major)); + hash.addData(reinterpret_cast<const char *>(&_minor), sizeof(_minor)); +} + +namespace LanguageUtils { + +bool operator<(const ComponentVersion &lhs, const ComponentVersion &rhs) +{ + return lhs.majorVersion() < rhs.majorVersion() + || (lhs.majorVersion() == rhs.majorVersion() && lhs.minorVersion() < rhs.minorVersion()); +} + +bool operator<=(const ComponentVersion &lhs, const ComponentVersion &rhs) +{ + return lhs.majorVersion() < rhs.majorVersion() + || (lhs.majorVersion() == rhs.majorVersion() && lhs.minorVersion() <= rhs.minorVersion()); +} + +bool operator>(const ComponentVersion &lhs, const ComponentVersion &rhs) +{ + return rhs < lhs; +} + +bool operator>=(const ComponentVersion &lhs, const ComponentVersion &rhs) +{ + return rhs <= lhs; +} + +bool operator==(const ComponentVersion &lhs, const ComponentVersion &rhs) +{ + return lhs.majorVersion() == rhs.majorVersion() && lhs.minorVersion() == rhs.minorVersion(); +} + +bool operator!=(const ComponentVersion &lhs, const ComponentVersion &rhs) +{ + return !(lhs == rhs); +} + +} diff --git a/tools/qmllint/componentversion.h b/tools/qmllint/componentversion.h new file mode 100644 index 0000000000..9d079f1d30 --- /dev/null +++ b/tools/qmllint/componentversion.h @@ -0,0 +1,73 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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 COMPONENTVERSION_H +#define COMPONENTVERSION_H + +#include <qglobal.h> + +QT_BEGIN_NAMESPACE +class QCryptographicHash; +QT_END_NAMESPACE + +namespace LanguageUtils { + +class ComponentVersion +{ + int _major; + int _minor; + +public: + static const int NoVersion; + static const int MaxVersion; + + ComponentVersion(); + ComponentVersion(int major, int minor); + explicit ComponentVersion(const QString &versionString); + ~ComponentVersion(); + + int majorVersion() const + { return _major; } + int minorVersion() const + { return _minor; } + + bool isValid() const; + QString toString() const; + void addToHash(QCryptographicHash &hash) const; +}; + +bool operator<(const ComponentVersion &lhs, const ComponentVersion &rhs); +bool operator<=(const ComponentVersion &lhs, const ComponentVersion &rhs); +bool operator>(const ComponentVersion &lhs, const ComponentVersion &rhs); +bool operator>=(const ComponentVersion &lhs, const ComponentVersion &rhs); +bool operator==(const ComponentVersion &lhs, const ComponentVersion &rhs); +bool operator!=(const ComponentVersion &lhs, const ComponentVersion &rhs); + +} // namespace LanguageUtils + +#endif // COMPONENTVERSION_H diff --git a/tools/qmllint/fakemetaobject.cpp b/tools/qmllint/fakemetaobject.cpp new file mode 100644 index 0000000000..514bb2fe42 --- /dev/null +++ b/tools/qmllint/fakemetaobject.cpp @@ -0,0 +1,600 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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 "fakemetaobject.h" +#include <QCryptographicHash> + +using namespace LanguageUtils; + +FakeMetaEnum::FakeMetaEnum() +{} + +FakeMetaEnum::FakeMetaEnum(const QString &name) + : m_name(name) +{} + +bool FakeMetaEnum::isValid() const +{ return !m_name.isEmpty(); } + +QString FakeMetaEnum::name() const +{ return m_name; } + +void FakeMetaEnum::setName(const QString &name) +{ m_name = name; } + +void FakeMetaEnum::addKey(const QString &key, int value) +{ m_keys.append(key); m_values.append(value); } + +QString FakeMetaEnum::key(int index) const +{ return m_keys.at(index); } + +int FakeMetaEnum::keyCount() const +{ return m_keys.size(); } + +QStringList FakeMetaEnum::keys() const +{ return m_keys; } + +bool FakeMetaEnum::hasKey(const QString &key) const +{ return m_keys.contains(key); } + +void FakeMetaEnum::addToHash(QCryptographicHash &hash) const +{ + int len = m_name.size(); + hash.addData(reinterpret_cast<const char *>(&len), sizeof(len)); + hash.addData(reinterpret_cast<const char *>(m_name.constData()), len * sizeof(QChar)); + len = m_keys.size(); + hash.addData(reinterpret_cast<const char *>(&len), sizeof(len)); + foreach (const QString &key, m_keys) { + len = key.size(); + hash.addData(reinterpret_cast<const char *>(&len), sizeof(len)); + hash.addData(reinterpret_cast<const char *>(key.constData()), len * sizeof(QChar)); + } + len = m_values.size(); + hash.addData(reinterpret_cast<const char *>(&len), sizeof(len)); + foreach (int value, m_values) + hash.addData(reinterpret_cast<const char *>(&value), sizeof(value)); +} + +QString FakeMetaEnum::describe(int baseIndent) const +{ + QString newLine = QString::fromLatin1("\n") + QString::fromLatin1(" ").repeated(baseIndent); + QString res = QLatin1String("Enum "); + res += name(); + res += QLatin1String(":{"); + for (int i = 0; i < keyCount(); ++i) { + res += newLine; + res += QLatin1String(" "); + res += key(i); + res += QLatin1String(": "); + res += QString::number(m_values.value(i, -1)); + } + res += newLine; + res += QLatin1Char('}'); + return res; +} + +QString FakeMetaEnum::toString() const +{ + return describe(); +} + +FakeMetaMethod::FakeMetaMethod(const QString &name, const QString &returnType) + : m_name(name) + , m_returnType(returnType) + , m_methodTy(FakeMetaMethod::Method) + , m_methodAccess(FakeMetaMethod::Public) + , m_revision(0) +{} + +FakeMetaMethod::FakeMetaMethod() + : m_methodTy(FakeMetaMethod::Method) + , m_methodAccess(FakeMetaMethod::Public) + , m_revision(0) +{} + +QString FakeMetaMethod::methodName() const +{ return m_name; } + +void FakeMetaMethod::setMethodName(const QString &name) +{ m_name = name; } + +void FakeMetaMethod::setReturnType(const QString &type) +{ m_returnType = type; } + +QStringList FakeMetaMethod::parameterNames() const +{ return m_paramNames; } + +QStringList FakeMetaMethod::parameterTypes() const +{ return m_paramTypes; } + +void FakeMetaMethod::addParameter(const QString &name, const QString &type) +{ m_paramNames.append(name); m_paramTypes.append(type); } + +int FakeMetaMethod::methodType() const +{ return m_methodTy; } + +void FakeMetaMethod::setMethodType(int methodType) +{ m_methodTy = methodType; } + +int FakeMetaMethod::access() const +{ return m_methodAccess; } + +int FakeMetaMethod::revision() const +{ return m_revision; } + +void FakeMetaMethod::setRevision(int r) +{ m_revision = r; } + +void FakeMetaMethod::addToHash(QCryptographicHash &hash) const +{ + int len = m_name.size(); + hash.addData(reinterpret_cast<const char *>(&len), sizeof(len)); + hash.addData(reinterpret_cast<const char *>(m_name.constData()), len * sizeof(QChar)); + hash.addData(reinterpret_cast<const char *>(&m_methodAccess), sizeof(m_methodAccess)); + hash.addData(reinterpret_cast<const char *>(&m_methodTy), sizeof(m_methodTy)); + hash.addData(reinterpret_cast<const char *>(&m_revision), sizeof(m_revision)); + len = m_paramNames.size(); + hash.addData(reinterpret_cast<const char *>(&len), sizeof(len)); + foreach (const QString &pName, m_paramNames) { + len = pName.size(); + hash.addData(reinterpret_cast<const char *>(&len), sizeof(len)); + hash.addData(reinterpret_cast<const char *>(pName.constData()), len * sizeof(QChar)); + } + len = m_paramTypes.size(); + hash.addData(reinterpret_cast<const char *>(&len), sizeof(len)); + foreach (const QString &pType, m_paramTypes) { + len = pType.size(); + hash.addData(reinterpret_cast<const char *>(&len), sizeof(len)); + hash.addData(reinterpret_cast<const char *>(pType.constData()), len * sizeof(QChar)); + } + len = m_returnType.size(); + hash.addData(reinterpret_cast<const char *>(&len), sizeof(len)); + hash.addData(reinterpret_cast<const char *>(m_returnType.constData()), len * sizeof(QChar)); +} + +QString FakeMetaMethod::describe(int baseIndent) const +{ + QString newLine = QString::fromLatin1("\n") + QString::fromLatin1(" ").repeated(baseIndent); + QString res = QLatin1String("Method {"); + res += newLine; + res += QLatin1String(" methodName:"); + res += methodName(); + res += newLine; + res += QLatin1String(" methodType:"); + res += methodType(); + res += newLine; + res += QLatin1String(" parameterNames:["); + foreach (const QString &pName, parameterNames()) { + res += newLine; + res += QLatin1String(" "); + res += pName; + } + res += QLatin1Char(']'); + res += newLine; + res += QLatin1String(" parameterTypes:["); + foreach (const QString &pType, parameterTypes()) { + res += newLine; + res += QLatin1String(" "); + res += pType; + } + res += QLatin1Char(']'); + res += newLine; + res += QLatin1Char('}'); + return res; +} + +QString FakeMetaMethod::toString() const +{ + return describe(); +} + + +FakeMetaProperty::FakeMetaProperty(const QString &name, const QString &type, bool isList, + bool isWritable, bool isPointer, int revision) + : m_propertyName(name) + , m_type(type) + , m_isList(isList) + , m_isWritable(isWritable) + , m_isPointer(isPointer) + , m_revision(revision) +{} + +QString FakeMetaProperty::name() const +{ return m_propertyName; } + +QString FakeMetaProperty::typeName() const +{ return m_type; } + +bool FakeMetaProperty::isList() const +{ return m_isList; } + +bool FakeMetaProperty::isWritable() const +{ return m_isWritable; } + +bool FakeMetaProperty::isPointer() const +{ return m_isPointer; } + +int FakeMetaProperty::revision() const +{ return m_revision; } + +void FakeMetaProperty::addToHash(QCryptographicHash &hash) const +{ + int len = m_propertyName.size(); + hash.addData(reinterpret_cast<const char *>(&len), sizeof(len)); + hash.addData(reinterpret_cast<const char *>(m_propertyName.constData()), len * sizeof(QChar)); + hash.addData(reinterpret_cast<const char *>(&m_revision), sizeof(m_revision)); + int flags = (m_isList ? (1 << 0) : 0) + + (m_isPointer ? (1 << 1) : 0) + + (m_isWritable ? (1 << 2) : 0); + hash.addData(reinterpret_cast<const char *>(&flags), sizeof(flags)); + len = m_type.size(); + hash.addData(reinterpret_cast<const char *>(&len), sizeof(len)); + hash.addData(reinterpret_cast<const char *>(m_type.constData()), len * sizeof(QChar)); +} + +QString FakeMetaProperty::describe(int baseIndent) const +{ + auto boolStr = [] (bool v) { return v ? QLatin1String("true") : QLatin1String("false"); }; + QString newLine = QString::fromLatin1("\n") + QString::fromLatin1(" ").repeated(baseIndent); + QString res = QLatin1String("Property {"); + res += newLine; + res += QLatin1String(" name:"); + res += name(); + res += newLine; + res += QLatin1String(" typeName:"); + res += typeName(); + res += newLine; + res += QLatin1String(" typeName:"); + res += QString::number(revision()); + res += newLine; + res += QLatin1String(" isList:"); + res += boolStr(isList()); + res += newLine; + res += QLatin1String(" isPointer:"); + res += boolStr(isPointer()); + res += newLine; + res += QLatin1String(" isWritable:"); + res += boolStr(isWritable()); + res += newLine; + res += QLatin1Char('}'); + return res; +} + +QString FakeMetaProperty::toString() const +{ + return describe(); +} + + +FakeMetaObject::FakeMetaObject() : m_isSingleton(false), m_isCreatable(true), m_isComposite(false) +{ +} + +QString FakeMetaObject::className() const +{ return m_className; } +void FakeMetaObject::setClassName(const QString &name) +{ m_className = name; } + +void FakeMetaObject::addExport(const QString &name, const QString &package, ComponentVersion version) +{ + Export exp; + exp.type = name; + exp.package = package; + exp.version = version; + m_exports.append(exp); +} + +void FakeMetaObject::setExportMetaObjectRevision(int exportIndex, int metaObjectRevision) +{ + m_exports[exportIndex].metaObjectRevision = metaObjectRevision; +} + +QList<FakeMetaObject::Export> FakeMetaObject::exports() const +{ return m_exports; } +FakeMetaObject::Export FakeMetaObject::exportInPackage(const QString &package) const +{ + foreach (const Export &exp, m_exports) { + if (exp.package == package) + return exp; + } + return Export(); +} + +void FakeMetaObject::setSuperclassName(const QString &superclass) +{ m_superName = superclass; } +QString FakeMetaObject::superclassName() const +{ return m_superName; } + +void FakeMetaObject::addEnum(const FakeMetaEnum &fakeEnum) +{ m_enumNameToIndex.insert(fakeEnum.name(), m_enums.size()); m_enums.append(fakeEnum); } +int FakeMetaObject::enumeratorCount() const +{ return m_enums.size(); } +int FakeMetaObject::enumeratorOffset() const +{ return 0; } +FakeMetaEnum FakeMetaObject::enumerator(int index) const +{ return m_enums.at(index); } +int FakeMetaObject::enumeratorIndex(const QString &name) const +{ return m_enumNameToIndex.value(name, -1); } + +void FakeMetaObject::addProperty(const FakeMetaProperty &property) +{ m_propNameToIdx.insert(property.name(), m_props.size()); m_props.append(property); } +int FakeMetaObject::propertyCount() const +{ return m_props.size(); } +int FakeMetaObject::propertyOffset() const +{ return 0; } +FakeMetaProperty FakeMetaObject::property(int index) const +{ return m_props.at(index); } +int FakeMetaObject::propertyIndex(const QString &name) const +{ return m_propNameToIdx.value(name, -1); } + +void FakeMetaObject::addMethod(const FakeMetaMethod &method) +{ m_methods.append(method); } +int FakeMetaObject::methodCount() const +{ return m_methods.size(); } +int FakeMetaObject::methodOffset() const +{ return 0; } +FakeMetaMethod FakeMetaObject::method(int index) const +{ return m_methods.at(index); } +int FakeMetaObject::methodIndex(const QString &name) const //If performances becomes an issue, just use a nameToIdx hash +{ + for (int i=0; i<m_methods.count(); i++) + if (m_methods[i].methodName() == name) + return i; + return -1; +} + +QString FakeMetaObject::defaultPropertyName() const +{ return m_defaultPropertyName; } +void FakeMetaObject::setDefaultPropertyName(const QString &defaultPropertyName) +{ m_defaultPropertyName = defaultPropertyName; } + +QString FakeMetaObject::attachedTypeName() const +{ return m_attachedTypeName; } +void FakeMetaObject::setAttachedTypeName(const QString &name) +{ m_attachedTypeName = name; } + +QByteArray FakeMetaObject::calculateFingerprint() const +{ + QCryptographicHash hash(QCryptographicHash::Sha1); + int len = m_className.size(); + hash.addData(reinterpret_cast<const char *>(&len), sizeof(len)); + hash.addData(reinterpret_cast<const char *>(m_className.constData()), len * sizeof(QChar)); + len = m_attachedTypeName.size(); + hash.addData(reinterpret_cast<const char *>(&len), sizeof(len)); + hash.addData(reinterpret_cast<const char *>(m_attachedTypeName.constData()), len * sizeof(QChar)); + len = m_defaultPropertyName.size(); + hash.addData(reinterpret_cast<const char *>(&len), sizeof(len)); + hash.addData(reinterpret_cast<const char *>(m_defaultPropertyName.constData()), len * sizeof(QChar)); + len = m_enumNameToIndex.size(); + hash.addData(reinterpret_cast<const char *>(&len), sizeof(len)); + { + QStringList keys(m_enumNameToIndex.keys()); + keys.sort(); + foreach (const QString &key, keys) { + len = key.size(); + hash.addData(reinterpret_cast<const char *>(&len), sizeof(len)); + hash.addData(reinterpret_cast<const char *>(key.constData()), len * sizeof(QChar)); + int value = m_enumNameToIndex.value(key); + hash.addData(reinterpret_cast<const char *>(&value), sizeof(value)); // avoid? this adds order dependency to fingerprint... + m_enums.at(value).addToHash(hash); + } + } + len = m_exports.size(); + hash.addData(reinterpret_cast<const char *>(&len), sizeof(len)); + foreach (const Export &e, m_exports) + e.addToHash(hash); // normalize order? + len = m_exports.size(); + hash.addData(reinterpret_cast<const char *>(&len), sizeof(len)); + foreach (const FakeMetaMethod &m, m_methods) + m.addToHash(hash); // normalize order? + { + QStringList keys(m_propNameToIdx.keys()); + keys.sort(); + foreach (const QString &key, keys) { + len = key.size(); + hash.addData(reinterpret_cast<const char *>(&len), sizeof(len)); + hash.addData(reinterpret_cast<const char *>(key.constData()), len * sizeof(QChar)); + int value = m_propNameToIdx.value(key); + hash.addData(reinterpret_cast<const char *>(&value), sizeof(value)); // avoid? this adds order dependency to fingerprint... + m_props.at(value).addToHash(hash); + } + } + len = m_superName.size(); + hash.addData(reinterpret_cast<const char *>(&len), sizeof(len)); + hash.addData(reinterpret_cast<const char *>(m_superName.constData()), len * sizeof(QChar)); + + QByteArray res = hash.result(); + res.append('F'); + return res; +} + +void FakeMetaObject::updateFingerprint() +{ + m_fingerprint = calculateFingerprint(); +} + +QByteArray FakeMetaObject::fingerprint() const +{ + return m_fingerprint; +} + +bool FakeMetaObject::isSingleton() const +{ + return m_isSingleton; +} + +bool FakeMetaObject::isCreatable() const +{ + return m_isCreatable; +} + +bool FakeMetaObject::isComposite() const +{ + return m_isComposite; +} + +void FakeMetaObject::setIsSingleton(bool value) +{ + m_isSingleton = value; +} + +void FakeMetaObject::setIsCreatable(bool value) +{ + m_isCreatable = value; +} + +void FakeMetaObject::setIsComposite(bool value) +{ + m_isSingleton = value; +} + +QString FakeMetaObject::toString() const +{ + return describe(); +} + +QString FakeMetaObject::describe(bool printDetails, int baseIndent) const +{ + QString res = QString::fromLatin1("FakeMetaObject@%1") + .arg((quintptr)(void *)this, 0, 16); + if (!printDetails) + return res; + auto boolStr = [] (bool v) { return v ? QLatin1String("true") : QLatin1String("false"); }; + QString newLine = QString::fromLatin1("\n") + QString::fromLatin1(" ").repeated(baseIndent); + res += QLatin1Char('{'); + res += newLine; + res += QLatin1String("className:"); + res += className(); + res += newLine; + res += QLatin1String("superClassName:"); + res += superclassName(); + res += newLine; + res += QLatin1String("isSingleton:"); + res += boolStr(isSingleton()); + res += newLine; + res += QLatin1String("isCreatable:"); + res += boolStr(isCreatable()); + res += newLine; + res += QLatin1String("isComposite:"); + res += boolStr(isComposite()); + res += newLine; + res += QLatin1String("defaultPropertyName:"); + res += defaultPropertyName(); + res += newLine; + res += QLatin1String("attachedTypeName:"); + res += attachedTypeName(); + res += newLine; + res += QLatin1String("fingerprint:"); + res += QString::fromUtf8(fingerprint()); + + res += newLine; + res += QLatin1String("exports:["); + foreach (const Export &e, exports()) { + res += newLine; + res += QLatin1String(" "); + res += e.describe(baseIndent + 2); + } + res += QLatin1Char(']'); + + res += newLine; + res += QLatin1String("enums:["); + for (int iEnum = 0; iEnum < enumeratorCount() ; ++ iEnum) { + FakeMetaEnum e = enumerator(enumeratorOffset() + iEnum); + res += newLine; + res += QLatin1String(" "); + res += e.describe(baseIndent + 2); + } + res += QLatin1Char(']'); + + res += newLine; + res += QLatin1String("properties:["); + for (int iProp = 0; iProp < propertyCount() ; ++ iProp) { + FakeMetaProperty prop = property(propertyOffset() + iProp); + res += newLine; + res += QLatin1String(" "); + res += prop.describe(baseIndent + 2); + } + res += QLatin1Char(']'); + res += QLatin1String("methods:["); + for (int iMethod = 0; iMethod < methodOffset() ; ++ iMethod) { + FakeMetaMethod m = method(methodOffset() + iMethod); + res += newLine; + res += QLatin1String(" "); + m.describe(baseIndent + 2); + } + res += QLatin1Char(']'); + res += newLine; + res += QLatin1Char('}'); + return res; +} + +FakeMetaObject::Export::Export() + : metaObjectRevision(0) +{} +bool FakeMetaObject::Export::isValid() const +{ return version.isValid() || !package.isEmpty() || !type.isEmpty(); } + +void FakeMetaObject::Export::addToHash(QCryptographicHash &hash) const +{ + int len = package.size(); + hash.addData(reinterpret_cast<const char *>(&len), sizeof(len)); + hash.addData(reinterpret_cast<const char *>(package.constData()), len * sizeof(QChar)); + len = type.size(); + hash.addData(reinterpret_cast<const char *>(&len), sizeof(len)); + hash.addData(reinterpret_cast<const char *>(type.constData()), len * sizeof(QChar)); + version.addToHash(hash); + hash.addData(reinterpret_cast<const char *>(&metaObjectRevision), sizeof(metaObjectRevision)); +} + +QString FakeMetaObject::Export::describe(int baseIndent) const +{ + QString newLine = QString::fromLatin1("\n") + QString::fromLatin1(" ").repeated(baseIndent); + QString res = QLatin1String("Export {"); + res += newLine; + res += QLatin1String(" package:"); + res += package; + res += newLine; + res += QLatin1String(" type:"); + res += type; + res += newLine; + res += QLatin1String(" version:"); + res += version.toString(); + res += newLine; + res += QLatin1String(" metaObjectRevision:"); + res += QString::number(metaObjectRevision); + res += newLine; + res += QLatin1String(" isValid:"); + res += QString::number(isValid()); + res += newLine; + res += QLatin1Char('}'); + return res; +} + +QString FakeMetaObject::Export::toString() const +{ + return describe(); +} diff --git a/tools/qmllint/fakemetaobject.h b/tools/qmllint/fakemetaobject.h new file mode 100644 index 0000000000..4e0ea1f8b3 --- /dev/null +++ b/tools/qmllint/fakemetaobject.h @@ -0,0 +1,236 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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 FAKEMETAOBJECT_H +#define FAKEMETAOBJECT_H + +#include <QString> +#include <QStringList> +#include <QList> +#include <QHash> +#include <QSharedPointer> + +#include "componentversion.h" + +QT_BEGIN_NAMESPACE +class QCryptographicHash; +QT_END_NAMESPACE + +namespace LanguageUtils { + +class FakeMetaEnum { + QString m_name; + QStringList m_keys; + QList<int> m_values; + +public: + FakeMetaEnum(); + explicit FakeMetaEnum(const QString &name); + + bool isValid() const; + + QString name() const; + void setName(const QString &name); + + void addKey(const QString &key, int value); + QString key(int index) const; + int keyCount() const; + QStringList keys() const; + bool hasKey(const QString &key) const; + void addToHash(QCryptographicHash &hash) const; + + QString describe(int baseIndent = 0) const; + QString toString() const; +}; + +class FakeMetaMethod { +public: + enum { + Signal, + Slot, + Method + }; + + enum { + Private, + Protected, + Public + }; + +public: + FakeMetaMethod(); + explicit FakeMetaMethod(const QString &name, const QString &returnType = QString()); + + QString methodName() const; + void setMethodName(const QString &name); + + void setReturnType(const QString &type); + + QStringList parameterNames() const; + QStringList parameterTypes() const; + void addParameter(const QString &name, const QString &type); + + int methodType() const; + void setMethodType(int methodType); + + int access() const; + + int revision() const; + void setRevision(int r); + void addToHash(QCryptographicHash &hash) const; + + QString describe(int baseIndent = 0) const; + QString toString() const; +private: + QString m_name; + QString m_returnType; + QStringList m_paramNames; + QStringList m_paramTypes; + int m_methodTy; + int m_methodAccess; + int m_revision; +}; + +class FakeMetaProperty { + QString m_propertyName; + QString m_type; + bool m_isList; + bool m_isWritable; + bool m_isPointer; + int m_revision; + +public: + FakeMetaProperty(const QString &name, const QString &type, bool isList, bool isWritable, bool isPointer, int revision); + + QString name() const; + QString typeName() const; + + bool isList() const; + bool isWritable() const; + bool isPointer() const; + int revision() const; + void addToHash(QCryptographicHash &hash) const; + + QString describe(int baseIndent = 0) const; + QString toString() const; +}; + +class FakeMetaObject { + Q_DISABLE_COPY(FakeMetaObject); + +public: + typedef QSharedPointer<FakeMetaObject> Ptr; + typedef QSharedPointer<const FakeMetaObject> ConstPtr; + + class Export { + public: + Export(); + + QString package; + QString type; + ComponentVersion version; + int metaObjectRevision; + + bool isValid() const; + void addToHash(QCryptographicHash &hash) const; + + QString describe(int baseIndent = 0) const; + QString toString() const; + }; + +private: + QString m_className; + QList<Export> m_exports; + QString m_superName; + QList<FakeMetaEnum> m_enums; + QHash<QString, int> m_enumNameToIndex; + QList<FakeMetaProperty> m_props; + QHash<QString, int> m_propNameToIdx; + QList<FakeMetaMethod> m_methods; + QString m_defaultPropertyName; + QString m_attachedTypeName; + QByteArray m_fingerprint; + bool m_isSingleton; + bool m_isCreatable; + bool m_isComposite; + +public: + FakeMetaObject(); + + QString className() const; + void setClassName(const QString &name); + + void addExport(const QString &name, const QString &package, ComponentVersion version); + void setExportMetaObjectRevision(int exportIndex, int metaObjectRevision); + QList<Export> exports() const; + Export exportInPackage(const QString &package) const; + + void setSuperclassName(const QString &superclass); + QString superclassName() const; + + void addEnum(const FakeMetaEnum &fakeEnum); + int enumeratorCount() const; + int enumeratorOffset() const; + FakeMetaEnum enumerator(int index) const; + int enumeratorIndex(const QString &name) const; + + void addProperty(const FakeMetaProperty &property); + int propertyCount() const; + int propertyOffset() const; + FakeMetaProperty property(int index) const; + int propertyIndex(const QString &name) const; + + void addMethod(const FakeMetaMethod &method); + int methodCount() const; + int methodOffset() const; + FakeMetaMethod method(int index) const; + int methodIndex(const QString &name) const; // Note: Returns any method with that name in case of overloads + + QString defaultPropertyName() const; + void setDefaultPropertyName(const QString &defaultPropertyName); + + QString attachedTypeName() const; + void setAttachedTypeName(const QString &name); + QByteArray calculateFingerprint() const; + void updateFingerprint(); + QByteArray fingerprint() const; + + bool isSingleton() const; + bool isCreatable() const; + bool isComposite() const; + void setIsSingleton(bool value); + void setIsCreatable(bool value); + void setIsComposite(bool value); + + QString describe(bool printDetails = true, int baseIndent = 0) const; + QString toString() const; +}; + +} // namespace LanguageUtils + +#endif // FAKEMETAOBJECT_H diff --git a/tools/qmllint/findunqualified.cpp b/tools/qmllint/findunqualified.cpp new file mode 100644 index 0000000000..49d64adb6e --- /dev/null +++ b/tools/qmllint/findunqualified.cpp @@ -0,0 +1,783 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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 "findunqualified.h" +#include "scopetree.h" + +#include "qmljstypedescriptionreader.h" + +#include <QFile> +#include <QDirIterator> +#include <QScopedValueRollback> + +#include <private/qqmljsast_p.h> +#include <private/qqmljslexer_p.h> +#include <private/qqmljsparser_p.h> +#include <private/qv4codegen_p.h> + +QDebug operator<<(QDebug dbg, const QQmlJS::AST::SourceLocation &loc); + +static QQmlJS::TypeDescriptionReader createReaderForFile(QString const &filename) +{ + QFile f(filename); + f.open(QFile::ReadOnly); + QQmlJS::TypeDescriptionReader reader { filename, f.readAll() }; + return reader; +} + +void FindUnqualifiedIDVisitor::enterEnvironment(ScopeType type, QString name) +{ + m_currentScope = m_currentScope->createNewChildScope(type, name); +} + +void FindUnqualifiedIDVisitor::leaveEnvironment() +{ + m_currentScope = m_currentScope->parentScope(); +} + +enum ImportVersion { FullyVersioned, PartiallyVersioned, Unversioned }; + +QStringList completeQmltypesPaths(const QString &uri, const QStringList &basePaths, int vmaj, int vmin) +{ + static const QLatin1Char Slash('/'); + static const QLatin1Char Backslash('\\'); + static const QLatin1String SlashPluginsDotQmltypes("/plugins.qmltypes"); + + const QVector<QStringRef> parts = uri.splitRef(QLatin1Char('.'), QString::SkipEmptyParts); + + QStringList qmlDirPathsPaths; + // fully & partially versioned parts + 1 unversioned for each base path + qmlDirPathsPaths.reserve(basePaths.count() * (2 * parts.count() + 1)); + + auto versionString = [](int vmaj, int vmin, ImportVersion version) + { + if (version == FullyVersioned) { + // extension with fully encoded version number (eg. MyModule.3.2) + return QString::asprintf(".%d.%d", vmaj, vmin); + } else if (version == PartiallyVersioned) { + // extension with encoded version major (eg. MyModule.3) + return QString::asprintf(".%d", vmaj); + } // else extension without version number (eg. MyModule) + return QString(); + }; + auto joinStringRefs = [](const QVector<QStringRef> &refs, const QChar &sep) + { + QString str; + for (auto it = refs.cbegin(); it != refs.cend(); ++it) { + if (it != refs.cbegin()) + str += sep; + str += *it; + } + return str; + }; + + for (int version = FullyVersioned; version <= Unversioned; ++version) { + const QString ver = versionString(vmaj, vmin, static_cast<ImportVersion>(version)); + + for (const QString &path : basePaths) { + QString dir = path; + if (!dir.endsWith(Slash) && !dir.endsWith(Backslash)) + dir += Slash; + + // append to the end + qmlDirPathsPaths += dir + joinStringRefs(parts, Slash) + ver + SlashPluginsDotQmltypes; + + if (version != Unversioned) { + // insert in the middle + for (int index = parts.count() - 2; index >= 0; --index) { + qmlDirPathsPaths += dir + joinStringRefs(parts.mid(0, index + 1), Slash) + + ver + Slash + + joinStringRefs(parts.mid(index + 1), Slash) + SlashPluginsDotQmltypes; + } + } + } + } + + return qmlDirPathsPaths; +} + +void FindUnqualifiedIDVisitor::importHelper(QString id, QString prefix, int major, int minor) +{ + QPair<QString, QString> importId { id, prefix }; + if (m_alreadySeenImports.contains(importId)) { + return; + } else { + m_alreadySeenImports.insert(importId); + } + id = id.replace(QLatin1String("/"), QLatin1String(".")); + auto qmltypesPaths = completeQmltypesPaths(id, m_qmltypeDirs, major, minor); + + QHash<QString, LanguageUtils::FakeMetaObject::ConstPtr> objects; + QList<QQmlJS::ModuleApiInfo> moduleApis; + QStringList dependencies; + for (auto const &qmltypesPath : qmltypesPaths) { + if (QFile::exists(qmltypesPath)) { + auto reader = createReaderForFile(qmltypesPath); + auto succ = reader(&objects, &moduleApis, &dependencies); + if (!succ) { + qDebug() << reader.errorMessage(); + } + break; + } + } + for (auto const &dependency : qAsConst(dependencies)) { + auto const split = dependency.split(" "); + auto const id = split.at(0); + auto const major = split.at(1).split('.').at(0).toInt(); + auto const minor = split.at(1).split('.').at(1).toInt(); + importHelper(id, QString(), major, minor); + } + // add objects + for (auto ob_it = objects.begin(); ob_it != objects.end(); ++ob_it) { + auto val = ob_it.value(); + m_exportedName2MetaObject[prefix + val->className()] = val; + for (auto export_ : val->exports()) { + m_exportedName2MetaObject[prefix + export_.type] = val; + } + for (auto enumCount = 0; enumCount < val->enumeratorCount(); ++enumCount) { + m_currentScope->insertQMLIdentifier(val->enumerator(enumCount).name()); + } + } +} + +LanguageUtils::FakeMetaObject * +FindUnqualifiedIDVisitor::localQmlFile2FakeMetaObject(QString filePath) +{ + using namespace QQmlJS::AST; + auto fake = new LanguageUtils::FakeMetaObject; + fake->setClassName(QFileInfo { filePath }.baseName()); + QFile file(filePath); + if (!file.open(QFile::ReadOnly)) { + return fake; + } + QString code = file.readAll(); + file.close(); + + QQmlJS::Engine engine; + QQmlJS::Lexer lexer(&engine); + + lexer.setCode(code, 1, true); + QQmlJS::Parser parser(&engine); + if (!parser.parse()) { + return fake; + } + QQmlJS::AST::UiProgram *program = parser.ast(); + auto header = program->headers; + while (header) { + if (auto import = cast<UiImport *>(header->headerItem)) { + if (import->version) { + QString path; + auto uri = import->importUri; + while (uri) { + path.append(uri->name); + path.append("/"); + uri = uri->next; + } + path.chop(1); + QString prefix = QLatin1String(""); + if (import->asToken.isValid()) { + prefix += import->importId + QLatin1Char('.'); + } + importHelper(path, prefix, import->version->majorVersion, import->version->minorVersion); + } + } + header = header->next; + } + auto member = program->members; + // member should be the sole element + Q_ASSERT(!member->next); + Q_ASSERT(member && member->member->kind == UiObjectMember::Kind_UiObjectDefinition); + auto definition = static_cast<UiObjectDefinition *>(member->member); + auto qualifiedId = definition->qualifiedTypeNameId; + while (qualifiedId && qualifiedId->next) { + qualifiedId = qualifiedId->next; + } + fake->setSuperclassName(qualifiedId->name.toString()); + UiObjectMemberList *initMembers = definition->initializer->members; + while (initMembers) { + switch (initMembers->member->kind) { + case UiObjectMember::Kind_UiArrayBinding: { + // nothing to do + break; + } + case UiObjectMember::Kind_UiEnumDeclaration: { + // nothing to do + break; + } + case UiObjectMember::Kind_UiObjectBinding: { + // nothing to do + break; + } + case UiObjectMember::Kind_UiObjectDefinition: { + // creates nothing accessible + break; + } + case UiObjectMember::Kind_UiPublicMember: { + auto publicMember = static_cast<UiPublicMember *>(initMembers->member); + switch (publicMember->type) { + case UiPublicMember::Signal: { + UiParameterList *param = publicMember->parameters; + LanguageUtils::FakeMetaMethod method; + method.setMethodType(LanguageUtils::FakeMetaMethod::Signal); + method.setMethodName(publicMember->name.toString()); + while (param) { + method.addParameter(param->name.toString(), param->type->name.toString()); + param = param->next; + } + fake->addMethod(method); + break; + } + case UiPublicMember::Property: { + LanguageUtils::FakeMetaProperty fakeprop { publicMember->name.toString(), + publicMember->typeModifier.toString(), + false, + false, + false, + 0 }; + fake->addProperty(fakeprop); + break; + } + } + break; + } + case UiObjectMember::Kind_UiScriptBinding: { + // does not create anything new, ignore + break; + } + case UiObjectMember::Kind_UiSourceElement: { + auto sourceElement = static_cast<UiSourceElement *>(initMembers->member); + if (FunctionExpression *fexpr = sourceElement->sourceElement->asFunctionDefinition()) { + LanguageUtils::FakeMetaMethod method; + method.setMethodType(LanguageUtils::FakeMetaMethod::Method); + FormalParameterList *parameters = fexpr->formals; + while (parameters) { + method.addParameter(parameters->element->bindingIdentifier.toString(), + ""); + parameters = parameters->next; + } + fake->addMethod(method); + } else if (ClassExpression *clexpr = + sourceElement->sourceElement->asClassDefinition()) { + LanguageUtils::FakeMetaProperty prop { + clexpr->name.toString(), "", false, false, false, 1 + }; + fake->addProperty(prop); + } else if (cast<VariableStatement *>(sourceElement->sourceElement)) { + // nothing to do + } else { + qDebug() << "unsupportedd sourceElement at" << sourceElement->firstSourceLocation() + << sourceElement->sourceElement->kind; + } + break; + } + default: { + qDebug() << "unsupported element of kind" << initMembers->member->kind; + } + } + initMembers = initMembers->next; + } + return fake; +} + +void FindUnqualifiedIDVisitor::importExportedNames(QStringRef prefix, QString name) +{ + for (;;) { + auto metaObject = m_exportedName2MetaObject[m_exportedName2MetaObject.contains(name) + ? name + : prefix + QLatin1Char('.') + name]; + if (metaObject) { + auto propertyCount = metaObject->propertyCount(); + for (auto i = 0; i < propertyCount; ++i) { + m_currentScope->insertPropertyIdentifier(metaObject->property(i).name()); + } + + m_currentScope->addMethodsFromMetaObject(metaObject); + + name = metaObject->superclassName(); + if (name.isEmpty() || name == QLatin1String("QObject")) { + break; + } + } else { + m_colorOut.write(QLatin1String("warning: "), Warning); + m_colorOut.write(name + QLatin1String(" was not found. Did you add all import paths?\n")); + m_unknownImports.insert(name); + break; + } + } +} + +void FindUnqualifiedIDVisitor::throwRecursionDepthError() +{ + m_colorOut.write(QStringLiteral("Error"), Error); + m_colorOut.write(QStringLiteral("Maximum statement or expression depth exceeded"), Error); + m_visitFailed = true; +} + +bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiProgram *) +{ + enterEnvironment(ScopeType::QMLScope, "program"); + QHash<QString, LanguageUtils::FakeMetaObject::ConstPtr> objects; + QList<QQmlJS::ModuleApiInfo> moduleApis; + QStringList dependencies; + for (auto const &dir : m_qmltypeDirs) { + QDirIterator it { dir, QStringList() << QLatin1String("builtins.qmltypes"), QDir::NoFilter, + QDirIterator::Subdirectories }; + while (it.hasNext()) { + auto reader = createReaderForFile(it.next()); + auto succ = reader(&objects, &moduleApis, &dependencies); + if (!succ) { + qDebug() << reader.errorMessage(); + } + } + } + // add builtins + for (auto ob_it = objects.begin(); ob_it != objects.end(); ++ob_it) { + auto val = ob_it.value(); + for (auto export_ : val->exports()) { + m_exportedName2MetaObject[export_.type] = val; + } + for (auto enumCount = 0; enumCount < val->enumeratorCount(); ++enumCount) { + m_currentScope->insertQMLIdentifier(val->enumerator(enumCount).name()); + } + } + // add "self" (as we only ever check the first part of a qualified identifier, we get away with + // using an empty FakeMetaObject + m_exportedName2MetaObject[QFileInfo { m_filePath }.baseName()] = {}; + + // add QML builtins + m_exportedName2MetaObject["QtObject"] = {}; // QtObject contains nothing of interest + + LanguageUtils::FakeMetaObject *meta = new LanguageUtils::FakeMetaObject{}; + meta->addProperty(LanguageUtils::FakeMetaProperty {"enabled", "bool", false, false, false, 0}); + meta->addProperty(LanguageUtils::FakeMetaProperty {"ignoreUnknownSignals", "bool", false, false, false, 0}); + meta->addProperty(LanguageUtils::FakeMetaProperty {"target", "QObject", false, false, false, 0}); + m_exportedName2MetaObject["Connections"] = LanguageUtils::FakeMetaObject::ConstPtr { meta }; + return true; +} + +void FindUnqualifiedIDVisitor::endVisit(QQmlJS::AST::UiProgram *) +{ + leaveEnvironment(); +} + +bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::ClassExpression *ast) +{ + enterEnvironment(ScopeType::JSFunctionScope, ast->name.toString()); + return true; +} + +void FindUnqualifiedIDVisitor::endVisit(QQmlJS::AST::ClassExpression *) +{ + leaveEnvironment(); +} + +bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::ClassDeclaration *ast) +{ + enterEnvironment(ScopeType::JSFunctionScope, ast->name.toString()); + return true; +} + +void FindUnqualifiedIDVisitor::endVisit(QQmlJS::AST::ClassDeclaration *) +{ + leaveEnvironment(); +} + +bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::ForStatement *) +{ + enterEnvironment(ScopeType::JSLexicalScope, "forloop"); + return true; +} + +void FindUnqualifiedIDVisitor::endVisit(QQmlJS::AST::ForStatement *) +{ + leaveEnvironment(); +} + +bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::ForEachStatement *) +{ + enterEnvironment(ScopeType::JSLexicalScope, "foreachloop"); + return true; +} + +void FindUnqualifiedIDVisitor::endVisit(QQmlJS::AST::ForEachStatement *) +{ + leaveEnvironment(); +} + +bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::Block *) +{ + enterEnvironment(ScopeType::JSLexicalScope, "block"); + return true; +} + +void FindUnqualifiedIDVisitor::endVisit(QQmlJS::AST::Block *) +{ + leaveEnvironment(); +} + +bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::CaseBlock *) +{ + enterEnvironment(ScopeType::JSLexicalScope, "case"); + return true; +} + +void FindUnqualifiedIDVisitor::endVisit(QQmlJS::AST::CaseBlock *) +{ + leaveEnvironment(); +} + +bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::Catch *catchStatement) +{ + enterEnvironment(ScopeType::JSLexicalScope, "catch"); + m_currentScope->insertJSIdentifier(catchStatement->patternElement->bindingIdentifier.toString(), QQmlJS::AST::VariableScope::Let); + return true; +} + +void FindUnqualifiedIDVisitor::endVisit(QQmlJS::AST::Catch *) +{ + leaveEnvironment(); +} + +bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::WithStatement *withStatement) +{ + m_colorOut.write(QString::asprintf("Warning: "), Warning); + m_colorOut.write(QString::asprintf("%d:%d: with statements are strongly discouraged in QML and might cause false positives when analysing unqalified identifiers\n", withStatement->firstSourceLocation().startLine, withStatement->firstSourceLocation().startColumn), Normal); + enterEnvironment(ScopeType::JSLexicalScope, "with"); + return true; +} + +void FindUnqualifiedIDVisitor::endVisit(QQmlJS::AST::WithStatement *) +{ + leaveEnvironment(); +} + +bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiScriptBinding *uisb) +{ + using namespace QQmlJS::AST; + auto name = uisb->qualifiedId->name; + if (name == QLatin1String("id")) { + // found id + auto expstat = static_cast<ExpressionStatement *>(uisb->statement); + auto identexp = static_cast<IdentifierExpression *>(expstat->expression); + QString elementName = m_currentScope->name(); + m_qmlid2meta.insert(identexp->name.toString(), m_exportedName2MetaObject[elementName]); + if (m_currentScope->isVisualRootScope()) { + m_rootId = identexp->name.toString(); + } + } else if (name.startsWith("on") && name.size() > 2 && name.at(2).isUpper()) { + auto statement = uisb->statement; + if (statement->kind == Node::Kind::Kind_ExpressionStatement) { + if (static_cast<ExpressionStatement *>(statement)->expression->asFunctionDefinition()) { + // functions are already handled + // they do not get names inserted according to the signal, but access their formal + // parameters + return true; + } + } + QString signal = name.mid(2).toString(); + signal[0] = signal[0].toLower(); + if (!m_currentScope->methods().contains(signal)) { + qDebug() << "Info file does not contain signal" << signal; + } else { + auto method = m_currentScope->methods()[signal]; + for (auto const ¶m : method.parameterNames()) { + auto firstSourceLocation = uisb->statement->firstSourceLocation(); + bool hasMultilineStatementBody = uisb->statement->lastSourceLocation().startLine > firstSourceLocation.startLine; + m_currentScope->insertSignalIdentifier(param, method, firstSourceLocation, hasMultilineStatementBody); + } + } + return true; + } + return true; +} + +bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiPublicMember *uipm) +{ + // property bool inactive: !active + // extract name inactive + m_currentScope->insertPropertyIdentifier(uipm->name.toString()); + return true; +} + +bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::IdentifierExpression *idexp) +{ + auto name = idexp->name; + if (!m_exportedName2MetaObject.contains(name.toString())) { + m_currentScope->addIdToAccssedIfNotInParentScopes( + { name.toString(), idexp->firstSourceLocation() }, m_unknownImports); + } + return true; +} + +FindUnqualifiedIDVisitor::FindUnqualifiedIDVisitor(QStringList const &qmltypeDirs, + const QString &code, const QString &fileName) + : m_rootScope(new ScopeTree { ScopeType::JSFunctionScope, "global" }), + m_currentScope(m_rootScope.get()), + m_qmltypeDirs(qmltypeDirs), + m_code(code), + m_rootId(QLatin1String("<id>")), + m_filePath(fileName) +{ + // setup color output + m_colorOut.insertColorMapping(Error, ColorOutput::RedForeground); + m_colorOut.insertColorMapping(Warning, ColorOutput::PurpleForeground); + m_colorOut.insertColorMapping(Info, ColorOutput::BlueForeground); + m_colorOut.insertColorMapping(Normal, ColorOutput::DefaultColor); + m_colorOut.insertColorMapping(Hint, ColorOutput::GreenForeground); + QLatin1String jsGlobVars[] = { + /* Not listed on the MDN page; browser and QML extensions: */ + // console/debug api + QLatin1String("console"), QLatin1String("print"), + // garbage collector + QLatin1String("gc"), + // i18n + QLatin1String("qsTr"), QLatin1String("qsTrId"), QLatin1String("QT_TR_NOOP"), QLatin1String("QT_TRANSLATE_NOOP"), QLatin1String("QT_TRID_NOOP"), + // XMLHttpRequest + QLatin1String("XMLHttpRequest") + }; + for (const char **globalName = QV4::Compiler::Codegen::s_globalNames; *globalName != nullptr; ++globalName) { + m_currentScope->insertJSIdentifier(QString::fromLatin1(*globalName), QQmlJS::AST::VariableScope::Const); + } + for (const auto& jsGlobVar: jsGlobVars) + m_currentScope->insertJSIdentifier(jsGlobVar, QQmlJS::AST::VariableScope::Const); +} + +FindUnqualifiedIDVisitor::~FindUnqualifiedIDVisitor() = default; + +bool FindUnqualifiedIDVisitor::check() +{ + if (m_visitFailed) + return false; + + // now that all ids are known, revisit any Connections whose target were perviously unknown + for (auto const& outstandingConnection: m_outstandingConnections) { + auto metaObject = m_qmlid2meta[outstandingConnection.targetName]; + outstandingConnection.scope->addMethodsFromMetaObject(metaObject); + QScopedValueRollback<ScopeTree*> rollback(m_currentScope, outstandingConnection.scope); + outstandingConnection.uiod->initializer->accept(this); + } + return m_rootScope->recheckIdentifiers(m_code, m_qmlid2meta, m_rootScope.get(), m_rootId, m_colorOut); +} + +bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::VariableDeclarationList *vdl) +{ + while (vdl) { + m_currentScope->insertJSIdentifier(vdl->declaration->bindingIdentifier.toString(), + vdl->declaration->scope); + vdl = vdl->next; + } + return true; +} + +void FindUnqualifiedIDVisitor::visitFunctionExpressionHelper(QQmlJS::AST::FunctionExpression *fexpr) +{ + using namespace QQmlJS::AST; + if (!fexpr->name.isEmpty()) { + auto name = fexpr->name.toString(); + if (m_currentScope->scopeType() == ScopeType::QMLScope) { + m_currentScope->insertQMLIdentifier(name); + } else { + m_currentScope->insertJSIdentifier(name, VariableScope::Const); + } + } + QString name = fexpr->name.toString(); + if (name.isEmpty()) + name = "<anon>"; + enterEnvironment(ScopeType::JSFunctionScope, name); +} + +bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::FunctionExpression *fexpr) +{ + visitFunctionExpressionHelper(fexpr); + return true; +} + +void FindUnqualifiedIDVisitor::endVisit(QQmlJS::AST::FunctionExpression *) +{ + leaveEnvironment(); +} + +bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::FunctionDeclaration *fdecl) +{ + visitFunctionExpressionHelper(fdecl); + return true; +} + +void FindUnqualifiedIDVisitor::endVisit(QQmlJS::AST::FunctionDeclaration *) +{ + leaveEnvironment(); +} + +bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::FormalParameterList *fpl) +{ + for (auto const &boundName : fpl->boundNames()) { + m_currentScope->insertJSIdentifier(boundName.id, QQmlJS::AST::VariableScope::Const); + } + return true; +} + +bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiImport *import) +{ + // construct path + QString prefix = QLatin1String(""); + if (import->asToken.isValid()) { + prefix += import->importId + QLatin1Char('.'); + } + auto dirname = import->fileName.toString(); + if (!dirname.isEmpty()) { + QFileInfo info { dirname }; + if (info.isRelative()) { + dirname = QDir(QFileInfo { m_filePath }.path()).filePath(dirname); + } + QDirIterator it { dirname, QStringList() << QLatin1String("*.qml"), QDir::NoFilter }; + while (it.hasNext()) { + LanguageUtils::FakeMetaObject *fake = localQmlFile2FakeMetaObject(it.next()); + m_exportedName2MetaObject.insert( + fake->className(), QSharedPointer<const LanguageUtils::FakeMetaObject>(fake)); + } + } + QString path {}; + if (!import->importId.isEmpty()) { + m_qmlid2meta.insert(import->importId.toString(), {}); // TODO: do not put imported ids into the same space as qml IDs + } + if (import->version) { + auto uri = import->importUri; + while (uri) { + path.append(uri->name); + path.append("/"); + uri = uri->next; + } + path.chop(1); + + importHelper(path, prefix, import->version->majorVersion, import->version->minorVersion); + } + return true; +} + +bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiEnumDeclaration *uied) +{ + m_currentScope->insertQMLIdentifier(uied->name.toString()); + return true; +} + +bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiObjectBinding *uiob) +{ + // property QtObject __styleData: QtObject {...} + m_currentScope->insertPropertyIdentifier(uiob->qualifiedId->name.toString()); + QString name {}; + auto id = uiob->qualifiedTypeNameId; + QStringRef prefix = uiob->qualifiedTypeNameId->name; + while (id) { + name += id->name.toString() + QLatin1Char('.'); + id = id->next; + } + name.chop(1); + enterEnvironment(ScopeType::QMLScope, name); + if (name == QLatin1String("Component") || name == QLatin1String("QtObject")) // there is no typeinfo for Component and QtObject, but they also have no interesting properties + return true; + importExportedNames(prefix, name); + return true; +} + +void FindUnqualifiedIDVisitor::endVisit(QQmlJS::AST::UiObjectBinding *) +{ + leaveEnvironment(); +} + +bool FindUnqualifiedIDVisitor::visit(QQmlJS::AST::UiObjectDefinition *uiod) +{ + QString name {}; + auto id = uiod->qualifiedTypeNameId; + QStringRef prefix = uiod->qualifiedTypeNameId->name; + while (id) { + name += id->name.toString() + QLatin1Char('.'); + id = id->next; + } + name.chop(1); + enterEnvironment(ScopeType::QMLScope, name); + if (name.isLower()) + return false; // Ignore grouped properties for now + if (name == QLatin1String("Component") || name == QLatin1String("QtObject")) // there is no typeinfo for Component + return true; + importExportedNames(prefix, name); + if (name.endsWith("Connections")) { + QString target; + auto member = uiod->initializer->members; + while (member) { + if (member->member->kind == QQmlJS::AST::Node::Kind_UiScriptBinding) { + auto asBinding = static_cast<QQmlJS::AST::UiScriptBinding*>(member->member); + if (asBinding->qualifiedId->name == QLatin1String("target")) { + if (asBinding->statement->kind == QQmlJS::AST::Node::Kind_ExpressionStatement) { + auto expr = static_cast<QQmlJS::AST::ExpressionStatement*>(asBinding->statement)->expression; + if (auto idexpr = QQmlJS::AST::cast<QQmlJS::AST::IdentifierExpression*>(expr)) { + target = idexpr->name.toString(); + } else { + // more complex expressions are not supported + } + } + break; + } + } + member = member->next; + } + LanguageUtils::FakeMetaObject::ConstPtr metaObject {}; + if (target.isEmpty()) { + // no target set, connection comes from parentF + ScopeTree* scope = m_currentScope; + do { + scope = scope->parentScope(); // TODO: rename method + } while (scope->scopeType() != ScopeType::QMLScope); + auto metaObject = m_exportedName2MetaObject[scope->name()]; + } else { + // there was a target, check if we already can find it + auto metaObjectIt = m_qmlid2meta.find(target); + if (metaObjectIt != m_qmlid2meta.end()) { + metaObject = *metaObjectIt; + } else { + m_outstandingConnections.push_back({target, m_currentScope, uiod}); + return false; // visit children later once target is known + } + } + m_currentScope->addMethodsFromMetaObject(metaObject); + } + return true; +} + +void FindUnqualifiedIDVisitor::endVisit(QQmlJS::AST::UiObjectDefinition *) +{ + leaveEnvironment(); +} + +QDebug operator<<(QDebug dbg, const QQmlJS::AST::SourceLocation &loc) +{ + QDebugStateSaver saver(dbg); + dbg.nospace() << loc.startLine; + dbg.nospace() << ":"; + dbg.nospace() << loc.startColumn; + return dbg.maybeSpace(); +} diff --git a/tools/qmllint/findunqualified.h b/tools/qmllint/findunqualified.h new file mode 100644 index 0000000000..181f42f265 --- /dev/null +++ b/tools/qmllint/findunqualified.h @@ -0,0 +1,131 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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 FINDUNQUALIFIED_H +#define FINDUNQUALIFIED_H + +#include "qmljstypedescriptionreader.h" +#include "qcoloroutput_p.h" + +#include <private/qqmljsastvisitor_p.h> +#include <private/qqmljsast_p.h> + +#include <QScopedPointer> + +class ScopeTree; +enum class ScopeType; + +class FindUnqualifiedIDVisitor : public QQmlJS::AST::Visitor { + +public: + explicit FindUnqualifiedIDVisitor(QStringList const &qmltypeDirs, const QString& code, const QString& fileName); + ~FindUnqualifiedIDVisitor() override; + bool check(); + +private: + QScopedPointer<ScopeTree> m_rootScope; + ScopeTree *m_currentScope; + QHash<QString, LanguageUtils::FakeMetaObject::ConstPtr> m_exportedName2MetaObject; + QStringList m_qmltypeDirs; + const QString& m_code; + QHash<QString, LanguageUtils::FakeMetaObject::ConstPtr> m_qmlid2meta; + QString m_rootId; + QString m_filePath; + QSet<QPair<QString, QString>> m_alreadySeenImports; + QSet<QString> m_unknownImports; + ColorOutput m_colorOut; + bool m_visitFailed = false; + + struct OutstandingConnection {QString targetName; ScopeTree *scope; QQmlJS::AST::UiObjectDefinition *uiod;}; + + QVarLengthArray<OutstandingConnection, 3> m_outstandingConnections; // Connections whose target we have not encountered + + void enterEnvironment(ScopeType type, QString name); + void leaveEnvironment(); + void importHelper(QString id, QString prefix, int major, int minor); + LanguageUtils::FakeMetaObject* localQmlFile2FakeMetaObject(QString filePath); + + + void importExportedNames(QStringRef prefix, QString name); + + void throwRecursionDepthError() override; + + // start block/scope handling + bool visit(QQmlJS::AST::UiProgram *ast) override; + void endVisit(QQmlJS::AST::UiProgram *ast) override; + + bool visit(QQmlJS::AST::ClassExpression *ast) override; + void endVisit(QQmlJS::AST::ClassExpression *ast) override; + + bool visit(QQmlJS::AST::ClassDeclaration *ast) override; + void endVisit(QQmlJS::AST::ClassDeclaration *ast) override; + + bool visit(QQmlJS::AST::ForStatement *ast) override; + void endVisit(QQmlJS::AST::ForStatement *ast) override; + + bool visit(QQmlJS::AST::ForEachStatement *ast) override; + void endVisit(QQmlJS::AST::ForEachStatement *ast) override; + + bool visit(QQmlJS::AST::Block *ast) override; + void endVisit(QQmlJS::AST::Block *ast) override; + + bool visit(QQmlJS::AST::CaseBlock *ast) override; + void endVisit(QQmlJS::AST::CaseBlock *ast) override; + + bool visit(QQmlJS::AST::Catch *ast) override; + void endVisit(QQmlJS::AST::Catch *ast) override; + + bool visit(QQmlJS::AST::WithStatement *withStatement) override; + void endVisit(QQmlJS::AST::WithStatement *ast) override; + + void visitFunctionExpressionHelper(QQmlJS::AST::FunctionExpression *fexpr); + bool visit(QQmlJS::AST::FunctionExpression *fexpr) override; + void endVisit(QQmlJS::AST::FunctionExpression *fexpr) override; + + bool visit(QQmlJS::AST::FunctionDeclaration *fdecl) override; + void endVisit(QQmlJS::AST::FunctionDeclaration *fdecl) override; + /* --- end block handling --- */ + + bool visit(QQmlJS::AST::VariableDeclarationList *vdl) override; + bool visit(QQmlJS::AST::FormalParameterList *fpl) override; + + bool visit(QQmlJS::AST::UiImport *import) override; + bool visit(QQmlJS::AST::UiEnumDeclaration *uied) override; + bool visit(QQmlJS::AST::UiObjectBinding *uiob) override; + void endVisit(QQmlJS::AST::UiObjectBinding *uiob) override; + bool visit(QQmlJS::AST::UiObjectDefinition *uiod) override; + void endVisit(QQmlJS::AST::UiObjectDefinition *) override; + bool visit(QQmlJS::AST::UiScriptBinding *uisb) override; + bool visit(QQmlJS::AST::UiPublicMember *uipm) override; + + // expression handling + bool visit(QQmlJS::AST::IdentifierExpression *idexp) override; +}; + + +#endif // FINDUNQUALIFIED_H diff --git a/tools/qmllint/main.cpp b/tools/qmllint/main.cpp index 791fb71685..235ec16c6e 100644 --- a/tools/qmllint/main.cpp +++ b/tools/qmllint/main.cpp @@ -34,12 +34,19 @@ #endif #include <QCoreApplication> -#include <private/qv4value_p.h> +#ifndef QT_BOOTSTRAPPED +#include <QLibraryInfo> +#endif + #include <private/qqmljslexer_p.h> #include <private/qqmljsparser_p.h> #include <private/qqmljsengine_p.h> +#include <private/qqmljsastvisitor_p.h> +#include <private/qqmljsast_p.h> + +#include "findunqualified.h" -static bool lint_file(const QString &filename, bool silent) +static bool lint_file(const QString &filename, const bool silent, const bool warnUnqualied, QStringList const &qmltypeDirs) { QFile file(filename); if (!file.open(QFile::ReadOnly)) { @@ -63,10 +70,17 @@ static bool lint_file(const QString &filename, bool silent) if (!success && !silent) { const auto diagnosticMessages = parser.diagnosticMessages(); for (const QQmlJS::DiagnosticMessage &m : diagnosticMessages) { - qWarning("%s:%d : %s", qPrintable(filename), m.loc.startLine, qPrintable(m.message)); + qWarning("%s:%d : %s", qPrintable(filename), m.line, qPrintable(m.message)); } } + if (success && !isJavaScript && warnUnqualied) { + auto root = parser.rootNode(); + FindUnqualifiedIDVisitor v { qmltypeDirs, code, filename}; + root->accept(&v); + success = v.check(); + } + return success; } @@ -80,8 +94,20 @@ int main(int argv, char *argc[]) parser.setApplicationDescription(QLatin1String("QML syntax verifier")); parser.addHelpOption(); parser.addVersionOption(); + QCommandLineOption silentOption(QStringList() << "s" << "silent", QLatin1String("Don't output syntax errors")); parser.addOption(silentOption); + + QCommandLineOption checkUnqualified(QStringList() << "U" << "check-unqualified", QLatin1String("Warn about unqualified identifiers")); + parser.addOption(checkUnqualified); + + QCommandLineOption qmltypesDirsOption( + QStringList() << "I" + << "qmldirs", + QLatin1String("Look for qmltypes files in specified directory"), + QLatin1String("directory")); + parser.addOption(qmltypesDirsOption); + parser.addPositionalArgument(QLatin1String("files"), QLatin1String("list of qml or js files to verify")); parser.process(app); @@ -92,8 +118,18 @@ int main(int argv, char *argc[]) } bool silent = parser.isSet(silentOption); + bool warnUnqualified = parser.isSet(checkUnqualified); + // use host qml import path as a sane default if nothing else has been provided + QStringList qmltypeDirs = parser.isSet(qmltypesDirsOption) ? parser.values(qmltypesDirsOption) +#ifndef QT_BOOTSTRAPPED + : QStringList{QLibraryInfo::location(QLibraryInfo::Qml2ImportsPath)}; +#else + : QStringList{}; +#endif #else bool silent = false; + bool warnUnqualified = false; + QStringList qmltypeDirs {}; #endif bool success = true; #if QT_CONFIG(commandlineparser) @@ -102,7 +138,7 @@ int main(int argv, char *argc[]) const auto arguments = app.arguments(); for (const QString &filename : arguments) #endif - success &= lint_file(filename, silent); + success &= lint_file(filename, silent, warnUnqualified, qmltypeDirs); return success ? 0 : -1; } diff --git a/tools/qmllint/qcoloroutput.cpp b/tools/qmllint/qcoloroutput.cpp new file mode 100644 index 0000000000..d2e723700a --- /dev/null +++ b/tools/qmllint/qcoloroutput.cpp @@ -0,0 +1,342 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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 <QFile> +#include <QHash> +#include <QTextCodec> + +#ifndef Q_OS_WIN +#include <unistd.h> +#endif + +#include "qcoloroutput_p.h" + +class ColorOutputPrivate +{ +public: + ColorOutputPrivate() : currentColorID(-1) + + { + /* - QIODevice::Unbuffered because we want it to appear when the user actually calls, performance + * is considered of lower priority. + */ + m_out.open(stderr, QIODevice::WriteOnly | QIODevice::Unbuffered); + + coloringEnabled = isColoringPossible(); + } + + ColorOutput::ColorMapping colorMapping; + int currentColorID; + bool coloringEnabled; + + static const char *const foregrounds[]; + static const char *const backgrounds[]; + + inline void write(const QString &msg) + { + m_out.write(msg.toLocal8Bit()); + } + + static QString escapeCode(const QString &in) + { + QString result; + result.append(QChar(0x1B)); + result.append(QLatin1Char('[')); + result.append(in); + result.append(QLatin1Char('m')); + return result; + } + +private: + QFile m_out; + + /*! + Returns true if it's suitable to send colored output to \c stderr. + */ + inline bool isColoringPossible() const + { +# if defined(Q_OS_WIN) + /* Windows doesn't at all support ANSI escape codes, unless + * the user install a "device driver". See the Wikipedia links in the + * class documentation for details. */ + return false; +# else + /* We use QFile::handle() to get the file descriptor. It's a bit unsure + * whether it's 2 on all platforms and in all cases, so hopefully this layer + * of abstraction helps handle such cases. */ + return isatty(m_out.handle()); +# endif + } +}; + +const char *const ColorOutputPrivate::foregrounds[] = +{ + "0;30", + "0;34", + "0;32", + "0;36", + "0;31", + "0;35", + "0;33", + "0;37", + "1;30", + "1;34", + "1;32", + "1;36", + "1;31", + "1;35", + "1;33", + "1;37" +}; + +const char *const ColorOutputPrivate::backgrounds[] = +{ + "0;40", + "0;44", + "0;42", + "0;46", + "0;41", + "0;45", + "0;43" +}; + +/*! + \class ColorOutput + \since 4.4 + \nonreentrant + \brief Outputs colored messages to \c stderr. + \internal + + ColorOutput is a convenience class for outputting messages to \c + stderr using color escape codes, as mandated in ECMA-48. ColorOutput + will only color output when it is detected to be suitable. For + instance, if \c stderr is detected to be attached to a file instead + of a TTY, no coloring will be done. + + ColorOutput does its best attempt. but it is generally undefined + what coloring or effect the various coloring flags has. It depends + strongly on what terminal software that is being used. + + When using `echo -e 'my escape sequence'`, \c{\033} works as an + initiator but not when printing from a C++ program, despite having + escaped the backslash. That's why we below use characters with + value 0x1B. + + It can be convenient to subclass ColorOutput with a private scope, + such that the functions are directly available in the class using + it. + + \section1 Usage + + To output messages, call write() or writeUncolored(). write() takes + as second argument an integer, which ColorOutput uses as a lookup + key to find the color it should color the text in. The mapping from + keys to colors is done using insertMapping(). Typically this is used + by having enums for the various kinds of messages, which + subsequently are registered. + + \code + enum MyMessage + { + Error, + Important + }; + + ColorOutput output; + output.insertMapping(Error, ColorOutput::RedForeground); + output.insertMapping(Import, ColorOutput::BlueForeground); + + output.write("This is important", Important); + output.write("Jack, I'm only the selected official!", Error); + \endcode + + \sa {http://tldp.org/HOWTO/Bash-Prompt-HOWTO/x329.html}{Bash Prompt HOWTO, 6.1. Colors}, + {http://linuxgazette.net/issue51/livingston-blade.html}{Linux Gazette, Tweaking Eterm, Edward Livingston-Blade}, + {http://www.ecma-international.org/publications/standards/Ecma-048.htm}{Standard ECMA-48, Control Functions for Coded Character Sets, ECMA International}, + {http://en.wikipedia.org/wiki/ANSI_escape_code}{Wikipedia, ANSI escape code}, + {http://linuxgazette.net/issue65/padala.html}{Linux Gazette, So You Like Color!, Pradeep Padala} + */ + +/*! + \enum ColorOutput::ColorCodeComponent + \value BlackForeground + \value BlueForeground + \value GreenForeground + \value CyanForeground + \value RedForeground + \value PurpleForeground + \value BrownForeground + \value LightGrayForeground + \value DarkGrayForeground + \value LightBlueForeground + \value LightGreenForeground + \value LightCyanForeground + \value LightRedForeground + \value LightPurpleForeground + \value YellowForeground + \value WhiteForeground + \value BlackBackground + \value BlueBackground + \value GreenBackground + \value CyanBackground + \value RedBackground + \value PurpleBackground + \value BrownBackground + + \value DefaultColor ColorOutput performs no coloring. This typically + means black on white or white on black, depending + on the settings of the user's terminal. + */ + +/*! + Sets the color mapping to be \a cMapping. + + Negative values are disallowed. + + \sa colorMapping(), insertMapping() + */ +void ColorOutput::setColorMapping(const ColorMapping &cMapping) +{ + d->colorMapping = cMapping; +} + +/*! + Returns the color mappings in use. + + \sa setColorMapping(), insertMapping() + */ +ColorOutput::ColorMapping ColorOutput::colorMapping() const +{ + return d->colorMapping; +} + +/*! + Constructs a ColorOutput instance, ready for use. + */ +ColorOutput::ColorOutput() : d(new ColorOutputPrivate()) +{ +} + +ColorOutput::~ColorOutput() = default; // must be here so that QScopedPointer has access to the complete type + +/*! + Sends \a message to \c stderr, using the color looked up in colorMapping() using \a colorID. + + If \a color isn't available in colorMapping(), result and behavior is undefined. + + If \a colorID is 0, which is the default value, the previously used coloring is used. ColorOutput + is initialized to not color at all. + + If \a message is empty, effects are undefined. + + \a message will be printed as is. For instance, no line endings will be inserted. + */ +void ColorOutput::write(const QString &message, int colorID) +{ + d->write(colorify(message, colorID)); +} + +/*! + Writes \a message to \c stderr as if for instance + QTextStream would have been used, and adds a line ending at the end. + + This function can be practical to use such that one can use ColorOutput for all forms of writing. + */ +void ColorOutput::writeUncolored(const QString &message) +{ + d->write(message + QLatin1Char('\n')); +} + +/*! + Treats \a message and \a colorID identically to write(), but instead of writing + \a message to \c stderr, it is prepared for being written to \c stderr, but is then + returned. + + This is useful when the colored string is inserted into a translated string(dividing + the string into several small strings prevents proper translation). + */ +QString ColorOutput::colorify(const QString &message, int colorID) const +{ + Q_ASSERT_X(colorID == -1 || d->colorMapping.contains(colorID), Q_FUNC_INFO, + qPrintable(QString::fromLatin1("There is no color registered by id %1").arg(colorID))); + Q_ASSERT_X(!message.isEmpty(), Q_FUNC_INFO, "It makes no sense to attempt to print an empty string."); + + if (colorID != -1) + d->currentColorID = colorID; + + if (d->coloringEnabled && colorID != -1) + { + const int color(d->colorMapping.value(colorID)); + + /* If DefaultColor is set, we don't want to color it. */ + if (color & DefaultColor) + return message; + + const int foregroundCode = (int(color) & ForegroundMask) >> ForegroundShift; + const int backgroundCode = (int(color) & BackgroundMask) >> BackgroundShift; + QString finalMessage; + bool closureNeeded = false; + + if (foregroundCode) + { + finalMessage.append(ColorOutputPrivate::escapeCode(QLatin1String(ColorOutputPrivate::foregrounds[foregroundCode - 1]))); + closureNeeded = true; + } + + if (backgroundCode) + { + finalMessage.append(ColorOutputPrivate::escapeCode(QLatin1String(ColorOutputPrivate::backgrounds[backgroundCode - 1]))); + closureNeeded = true; + } + + finalMessage.append(message); + + if (closureNeeded) + { + finalMessage.append(QChar(0x1B)); + finalMessage.append(QLatin1String("[0m")); + } + + return finalMessage; + } + else + return message; +} + +/*! + Adds a color mapping from \a colorID to \a colorCode, for this ColorOutput instance. + + This is a convenience function for creating a ColorOutput::ColorMapping instance and + calling setColorMapping(). + + \sa colorMapping(), setColorMapping() + */ +void ColorOutput::insertColorMapping(int colorID, const ColorCode colorCode) +{ + d->colorMapping.insert(colorID, colorCode); +} diff --git a/tools/qmllint/qcoloroutput_p.h b/tools/qmllint/qcoloroutput_p.h new file mode 100644 index 0000000000..710bf5db74 --- /dev/null +++ b/tools/qmllint/qcoloroutput_p.h @@ -0,0 +1,110 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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$ +** +****************************************************************************/ + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. + +#ifndef QCOLOROUTPUT_P_H +#define QCOLOROUTPUT_P_H + +#include <QtCore/QtGlobal> +#include <QtCore/QHash> +#include <QScopedPointer> + +class ColorOutputPrivate; + +class ColorOutput +{ + enum + { + ForegroundShift = 10, + BackgroundShift = 20, + SpecialShift = 20, + ForegroundMask = 0x1f << ForegroundShift, + BackgroundMask = 0x7 << BackgroundShift + }; + +public: + enum ColorCodeComponent + { + BlackForeground = 1 << ForegroundShift, + BlueForeground = 2 << ForegroundShift, + GreenForeground = 3 << ForegroundShift, + CyanForeground = 4 << ForegroundShift, + RedForeground = 5 << ForegroundShift, + PurpleForeground = 6 << ForegroundShift, + BrownForeground = 7 << ForegroundShift, + LightGrayForeground = 8 << ForegroundShift, + DarkGrayForeground = 9 << ForegroundShift, + LightBlueForeground = 10 << ForegroundShift, + LightGreenForeground = 11 << ForegroundShift, + LightCyanForeground = 12 << ForegroundShift, + LightRedForeground = 13 << ForegroundShift, + LightPurpleForeground = 14 << ForegroundShift, + YellowForeground = 15 << ForegroundShift, + WhiteForeground = 16 << ForegroundShift, + + BlackBackground = 1 << BackgroundShift, + BlueBackground = 2 << BackgroundShift, + GreenBackground = 3 << BackgroundShift, + CyanBackground = 4 << BackgroundShift, + RedBackground = 5 << BackgroundShift, + PurpleBackground = 6 << BackgroundShift, + BrownBackground = 7 << BackgroundShift, + DefaultColor = 1 << SpecialShift + }; + + typedef QFlags<ColorCodeComponent> ColorCode; + typedef QHash<int, ColorCode> ColorMapping; + + ColorOutput(); + ~ColorOutput(); + + void setColorMapping(const ColorMapping &cMapping); + ColorMapping colorMapping() const; + void insertColorMapping(int colorID, const ColorCode colorCode); + + void writeUncolored(const QString &message); + void write(const QString &message, int color = -1); + QString colorify(const QString &message, int color = -1) const; + +private: + QScopedPointer<ColorOutputPrivate> d; + Q_DISABLE_COPY(ColorOutput) +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(ColorOutput::ColorCode) + +#endif diff --git a/tools/qmllint/qmljstypedescriptionreader.cpp b/tools/qmllint/qmljstypedescriptionreader.cpp new file mode 100644 index 0000000000..542cdf99eb --- /dev/null +++ b/tools/qmllint/qmljstypedescriptionreader.cpp @@ -0,0 +1,704 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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 "qmljstypedescriptionreader.h" + +#include <private/qqmljsparser_p.h> +#include <private/qqmljslexer_p.h> +#include <private/qqmljsengine_p.h> + +#include <QDir> + +#define QTC_ASSERT_STRINGIFY_HELPER(x) #x +#define QTC_ASSERT_STRINGIFY(x) QTC_ASSERT_STRINGIFY_HELPER(x) +#define QTC_ASSERT_STRING(cond) qDebug() << (\ + "\"" cond"\" in file " __FILE__ ", line " QTC_ASSERT_STRINGIFY(__LINE__)) +#define QTC_ASSERT(cond, action) if (Q_LIKELY(cond)) {} else { QTC_ASSERT_STRING(#cond); action; } do {} while (0) + +using namespace QQmlJS; +using namespace QQmlJS::AST; +using namespace LanguageUtils; + +QString toString(const AST::UiQualifiedId *qualifiedId, QChar delimiter = QLatin1Char('.')) +{ + QString result; + + for (const UiQualifiedId *iter = qualifiedId; iter; iter = iter->next) { + if (iter != qualifiedId) + result += delimiter; + + result += iter->name; + } + + return result; +} + +TypeDescriptionReader::TypeDescriptionReader(const QString &fileName, const QString &data) + : _fileName (fileName), _source(data), _objects(0) +{ +} + +TypeDescriptionReader::~TypeDescriptionReader() +{ +} + +bool TypeDescriptionReader::operator()( + QHash<QString, FakeMetaObject::ConstPtr> *objects, + QList<ModuleApiInfo> *moduleApis, + QStringList *dependencies) +{ + Engine engine; + + Lexer lexer(&engine); + Parser parser(&engine); + + lexer.setCode(_source, /*line = */ 1, /*qmlMode = */true); + + if (!parser.parse()) { + _errorMessage = QString::fromLatin1("%1:%2: %3").arg( + QString::number(parser.errorLineNumber()), + QString::number(parser.errorColumnNumber()), + parser.errorMessage()); + return false; + } + + _objects = objects; + _moduleApis = moduleApis; + _dependencies = dependencies; + readDocument(parser.ast()); + + return _errorMessage.isEmpty(); +} + +QString TypeDescriptionReader::errorMessage() const +{ + return _errorMessage; +} + +QString TypeDescriptionReader::warningMessage() const +{ + return _warningMessage; +} + +void TypeDescriptionReader::readDocument(UiProgram *ast) +{ + if (!ast) { + addError(SourceLocation(), tr("Could not parse document.")); + return; + } + + if (!ast->headers || ast->headers->next || !AST::cast<AST::UiImport *>(ast->headers->headerItem)) { + addError(SourceLocation(), tr("Expected a single import.")); + return; + } + + UiImport *import = AST::cast<AST::UiImport *>(ast->headers->headerItem); + if (toString(import->importUri) != QLatin1String("QtQuick.tooling")) { + addError(import->importToken, tr("Expected import of QtQuick.tooling.")); + return; + } + + ComponentVersion version; + const QString versionString = _source.mid(import->versionToken.offset, import->versionToken.length); + const int dotIdx = versionString.indexOf(QLatin1Char('.')); + if (dotIdx != -1) { + version = ComponentVersion(versionString.leftRef(dotIdx).toInt(), + versionString.midRef(dotIdx + 1).toInt()); + } + if (version.majorVersion() != 1) { + addError(import->versionToken, tr("Major version different from 1 not supported.")); + return; + } + + if (!ast->members || !ast->members->member || ast->members->next) { + addError(SourceLocation(), tr("Expected document to contain a single object definition.")); + return; + } + + UiObjectDefinition *module = AST::cast<UiObjectDefinition *>(ast->members->member); + if (!module) { + addError(SourceLocation(), tr("Expected document to contain a single object definition.")); + return; + } + + if (toString(module->qualifiedTypeNameId) != QLatin1String("Module")) { + addError(SourceLocation(), tr("Expected document to contain a Module {} member.")); + return; + } + + readModule(module); +} + +void TypeDescriptionReader::readModule(UiObjectDefinition *ast) +{ + for (UiObjectMemberList *it = ast->initializer->members; it; it = it->next) { + UiObjectMember *member = it->member; + UiObjectDefinition *component = AST::cast<UiObjectDefinition *>(member); + + UiScriptBinding *script = AST::cast<UiScriptBinding *>(member); + if (script && (toString(script->qualifiedId) == QStringLiteral("dependencies"))) { + readDependencies(script); + continue; + } + + QString typeName; + if (component) + typeName = toString(component->qualifiedTypeNameId); + + if (!component || (typeName != QLatin1String("Component") && typeName != QLatin1String("ModuleApi"))) { + continue; + } + + if (typeName == QLatin1String("Component")) + readComponent(component); + else if (typeName == QLatin1String("ModuleApi")) + readModuleApi(component); + } +} + +void TypeDescriptionReader::addError(const SourceLocation &loc, const QString &message) +{ + _errorMessage += QString::fromLatin1("%1:%2:%3: %4\n").arg( + QDir::toNativeSeparators(_fileName), + QString::number(loc.startLine), + QString::number(loc.startColumn), + message); +} + +void TypeDescriptionReader::addWarning(const SourceLocation &loc, const QString &message) +{ + _warningMessage += QString::fromLatin1("%1:%2:%3: %4\n").arg( + QDir::toNativeSeparators(_fileName), + QString::number(loc.startLine), + QString::number(loc.startColumn), + message); +} + +void TypeDescriptionReader::readDependencies(UiScriptBinding *ast) +{ + ExpressionStatement *stmt = AST::cast<ExpressionStatement*>(ast->statement); + if (!stmt) { + addError(ast->statement->firstSourceLocation(), tr("Expected dependency definitions")); + return; + } + ArrayPattern *exp = AST::cast<ArrayPattern *>(stmt->expression); + if (!exp) { + addError(stmt->expression->firstSourceLocation(), tr("Expected dependency definitions")); + return; + } + for (PatternElementList *l = exp->elements; l; l = l->next) { + //StringLiteral *str = AST::cast<StringLiteral *>(l->element->initializer); + StringLiteral *str = AST::cast<StringLiteral *>(l->element->initializer); + *_dependencies << str->value.toString(); + } +} + +void TypeDescriptionReader::readComponent(UiObjectDefinition *ast) +{ + FakeMetaObject::Ptr fmo(new FakeMetaObject); + + for (UiObjectMemberList *it = ast->initializer->members; it; it = it->next) { + UiObjectMember *member = it->member; + UiObjectDefinition *component = AST::cast<UiObjectDefinition *>(member); + UiScriptBinding *script = AST::cast<UiScriptBinding *>(member); + if (component) { + QString name = toString(component->qualifiedTypeNameId); + if (name == QLatin1String("Property")) + readProperty(component, fmo); + else if (name == QLatin1String("Method") || name == QLatin1String("Signal")) + readSignalOrMethod(component, name == QLatin1String("Method"), fmo); + else if (name == QLatin1String("Enum")) + readEnum(component, fmo); + else + addWarning(component->firstSourceLocation(), + tr("Expected only Property, Method, Signal and Enum object definitions, not \"%1\".") + .arg(name)); + } else if (script) { + QString name = toString(script->qualifiedId); + if (name == QLatin1String("name")) { + fmo->setClassName(readStringBinding(script)); + } else if (name == QLatin1String("prototype")) { + fmo->setSuperclassName(readStringBinding(script)); + } else if (name == QLatin1String("defaultProperty")) { + fmo->setDefaultPropertyName(readStringBinding(script)); + } else if (name == QLatin1String("exports")) { + readExports(script, fmo); + } else if (name == QLatin1String("exportMetaObjectRevisions")) { + readMetaObjectRevisions(script, fmo); + } else if (name == QLatin1String("attachedType")) { + fmo->setAttachedTypeName(readStringBinding(script)); + } else if (name == QLatin1String("isSingleton")) { + fmo->setIsSingleton(readBoolBinding(script)); + } else if (name == QLatin1String("isCreatable")) { + fmo->setIsCreatable(readBoolBinding(script)); + } else if (name == QLatin1String("isComposite")) { + fmo->setIsComposite(readBoolBinding(script)); + } else { + addWarning(script->firstSourceLocation(), + tr("Expected only name, prototype, defaultProperty, attachedType, exports, " + "isSingleton, isCreatable, isComposite and exportMetaObjectRevisions " + "script bindings, not \"%1\".").arg(name)); + } + } else { + addWarning(member->firstSourceLocation(), tr("Expected only script bindings and object definitions.")); + } + } + + if (fmo->className().isEmpty()) { + addError(ast->firstSourceLocation(), tr("Component definition is missing a name binding.")); + return; + } + + // ### add implicit export into the package of c++ types + fmo->addExport(fmo->className(), QStringLiteral("<cpp>"), ComponentVersion()); + fmo->updateFingerprint(); + _objects->insert(fmo->className(), fmo); +} + +void TypeDescriptionReader::readModuleApi(UiObjectDefinition *ast) +{ + ModuleApiInfo apiInfo; + + for (UiObjectMemberList *it = ast->initializer->members; it; it = it->next) { + UiObjectMember *member = it->member; + UiScriptBinding *script = AST::cast<UiScriptBinding *>(member); + + if (script) { + const QString name = toString(script->qualifiedId); + if (name == QLatin1String("uri")) { + apiInfo.uri = readStringBinding(script); + } else if (name == QLatin1String("version")) { + apiInfo.version = readNumericVersionBinding(script); + } else if (name == QLatin1String("name")) { + apiInfo.cppName = readStringBinding(script); + } else { + addWarning(script->firstSourceLocation(), + tr("Expected only uri, version and name script bindings.")); + } + } else { + addWarning(member->firstSourceLocation(), tr("Expected only script bindings.")); + } + } + + if (!apiInfo.version.isValid()) { + addError(ast->firstSourceLocation(), tr("ModuleApi definition has no or invalid version binding.")); + return; + } + + if (_moduleApis) + _moduleApis->append(apiInfo); +} + +void TypeDescriptionReader::readSignalOrMethod(UiObjectDefinition *ast, bool isMethod, FakeMetaObject::Ptr fmo) +{ + FakeMetaMethod fmm; + // ### confusion between Method and Slot. Method should be removed. + if (isMethod) + fmm.setMethodType(FakeMetaMethod::Slot); + else + fmm.setMethodType(FakeMetaMethod::Signal); + + for (UiObjectMemberList *it = ast->initializer->members; it; it = it->next) { + UiObjectMember *member = it->member; + UiObjectDefinition *component = AST::cast<UiObjectDefinition *>(member); + UiScriptBinding *script = AST::cast<UiScriptBinding *>(member); + if (component) { + QString name = toString(component->qualifiedTypeNameId); + if (name == QLatin1String("Parameter")) + readParameter(component, &fmm); + else + addWarning(component->firstSourceLocation(), tr("Expected only Parameter object definitions.")); + } else if (script) { + QString name = toString(script->qualifiedId); + if (name == QLatin1String("name")) + fmm.setMethodName(readStringBinding(script)); + else if (name == QLatin1String("type")) + fmm.setReturnType(readStringBinding(script)); + else if (name == QLatin1String("revision")) + fmm.setRevision(readIntBinding(script)); + else + addWarning(script->firstSourceLocation(), tr("Expected only name and type script bindings.")); + + } else { + addWarning(member->firstSourceLocation(), tr("Expected only script bindings and object definitions.")); + } + } + + if (fmm.methodName().isEmpty()) { + addError(ast->firstSourceLocation(), tr("Method or signal is missing a name script binding.")); + return; + } + + fmo->addMethod(fmm); +} + +void TypeDescriptionReader::readProperty(UiObjectDefinition *ast, FakeMetaObject::Ptr fmo) +{ + QString name; + QString type; + bool isPointer = false; + bool isReadonly = false; + bool isList = false; + int revision = 0; + + for (UiObjectMemberList *it = ast->initializer->members; it; it = it->next) { + UiObjectMember *member = it->member; + UiScriptBinding *script = AST::cast<UiScriptBinding *>(member); + if (!script) { + addWarning(member->firstSourceLocation(), tr("Expected script binding.")); + continue; + } + + QString id = toString(script->qualifiedId); + if (id == QLatin1String("name")) + name = readStringBinding(script); + else if (id == QLatin1String("type")) + type = readStringBinding(script); + else if (id == QLatin1String("isPointer")) + isPointer = readBoolBinding(script); + else if (id == QLatin1String("isReadonly")) + isReadonly = readBoolBinding(script); + else if (id == QLatin1String("isList")) + isList = readBoolBinding(script); + else if (id == QLatin1String("revision")) + revision = readIntBinding(script); + else + addWarning(script->firstSourceLocation(), tr("Expected only type, name, revision, isPointer, isReadonly and isList script bindings.")); + } + + if (name.isEmpty() || type.isEmpty()) { + addError(ast->firstSourceLocation(), tr("Property object is missing a name or type script binding.")); + return; + } + + fmo->addProperty(FakeMetaProperty(name, type, isList, !isReadonly, isPointer, revision)); +} + +void TypeDescriptionReader::readEnum(UiObjectDefinition *ast, FakeMetaObject::Ptr fmo) +{ + FakeMetaEnum fme; + + for (UiObjectMemberList *it = ast->initializer->members; it; it = it->next) { + UiObjectMember *member = it->member; + UiScriptBinding *script = AST::cast<UiScriptBinding *>(member); + if (!script) { + addWarning(member->firstSourceLocation(), tr("Expected script binding.")); + continue; + } + + QString name = toString(script->qualifiedId); + if (name == QLatin1String("name")) + fme.setName(readStringBinding(script)); + else if (name == QLatin1String("values")) + readEnumValues(script, &fme); + else + addWarning(script->firstSourceLocation(), tr("Expected only name and values script bindings.")); + } + + fmo->addEnum(fme); +} + +void TypeDescriptionReader::readParameter(UiObjectDefinition *ast, FakeMetaMethod *fmm) +{ + QString name; + QString type; + + for (UiObjectMemberList *it = ast->initializer->members; it; it = it->next) { + UiObjectMember *member = it->member; + UiScriptBinding *script = AST::cast<UiScriptBinding *>(member); + if (!script) { + addWarning(member->firstSourceLocation(), tr("Expected script binding.")); + continue; + } + + const QString id = toString(script->qualifiedId); + if (id == QLatin1String("name")) { + name = readStringBinding(script); + } else if (id == QLatin1String("type")) { + type = readStringBinding(script); + } else if (id == QLatin1String("isPointer")) { + // ### unhandled + } else if (id == QLatin1String("isReadonly")) { + // ### unhandled + } else if (id == QLatin1String("isList")) { + // ### unhandled + } else { + addWarning(script->firstSourceLocation(), tr("Expected only name and type script bindings.")); + } + } + + fmm->addParameter(name, type); +} + +QString TypeDescriptionReader::readStringBinding(UiScriptBinding *ast) +{ + QTC_ASSERT(ast, return QString()); + + if (!ast->statement) { + addError(ast->colonToken, tr("Expected string after colon.")); + return QString(); + } + + ExpressionStatement *expStmt = AST::cast<ExpressionStatement *>(ast->statement); + if (!expStmt) { + addError(ast->statement->firstSourceLocation(), tr("Expected string after colon.")); + return QString(); + } + + StringLiteral *stringLit = AST::cast<StringLiteral *>(expStmt->expression); + if (!stringLit) { + addError(expStmt->firstSourceLocation(), tr("Expected string after colon.")); + return QString(); + } + + return stringLit->value.toString(); +} + +bool TypeDescriptionReader::readBoolBinding(AST::UiScriptBinding *ast) +{ + QTC_ASSERT(ast, return false); + + if (!ast->statement) { + addError(ast->colonToken, tr("Expected boolean after colon.")); + return false; + } + + ExpressionStatement *expStmt = AST::cast<ExpressionStatement *>(ast->statement); + if (!expStmt) { + addError(ast->statement->firstSourceLocation(), tr("Expected boolean after colon.")); + return false; + } + + TrueLiteral *trueLit = AST::cast<TrueLiteral *>(expStmt->expression); + FalseLiteral *falseLit = AST::cast<FalseLiteral *>(expStmt->expression); + if (!trueLit && !falseLit) { + addError(expStmt->firstSourceLocation(), tr("Expected true or false after colon.")); + return false; + } + + return trueLit; +} + +double TypeDescriptionReader::readNumericBinding(AST::UiScriptBinding *ast) +{ + QTC_ASSERT(ast, return qQNaN()); + + if (!ast->statement) { + addError(ast->colonToken, tr("Expected numeric literal after colon.")); + return 0; + } + + ExpressionStatement *expStmt = AST::cast<ExpressionStatement *>(ast->statement); + if (!expStmt) { + addError(ast->statement->firstSourceLocation(), tr("Expected numeric literal after colon.")); + return 0; + } + + NumericLiteral *numericLit = AST::cast<NumericLiteral *>(expStmt->expression); + if (!numericLit) { + addError(expStmt->firstSourceLocation(), tr("Expected numeric literal after colon.")); + return 0; + } + + return numericLit->value; +} + +ComponentVersion TypeDescriptionReader::readNumericVersionBinding(UiScriptBinding *ast) +{ + ComponentVersion invalidVersion; + + if (!ast || !ast->statement) { + addError((ast ? ast->colonToken : SourceLocation()), tr("Expected numeric literal after colon.")); + return invalidVersion; + } + + ExpressionStatement *expStmt = AST::cast<ExpressionStatement *>(ast->statement); + if (!expStmt) { + addError(ast->statement->firstSourceLocation(), tr("Expected numeric literal after colon.")); + return invalidVersion; + } + + NumericLiteral *numericLit = AST::cast<NumericLiteral *>(expStmt->expression); + if (!numericLit) { + addError(expStmt->firstSourceLocation(), tr("Expected numeric literal after colon.")); + return invalidVersion; + } + + return ComponentVersion(_source.mid(numericLit->literalToken.begin(), numericLit->literalToken.length)); +} + +int TypeDescriptionReader::readIntBinding(AST::UiScriptBinding *ast) +{ + double v = readNumericBinding(ast); + int i = static_cast<int>(v); + + if (i != v) { + addError(ast->firstSourceLocation(), tr("Expected integer after colon.")); + return 0; + } + + return i; +} + +void TypeDescriptionReader::readExports(UiScriptBinding *ast, FakeMetaObject::Ptr fmo) +{ + QTC_ASSERT(ast, return); + + if (!ast->statement) { + addError(ast->colonToken, tr("Expected array of strings after colon.")); + return; + } + + ExpressionStatement *expStmt = AST::cast<ExpressionStatement *>(ast->statement); + if (!expStmt) { + addError(ast->statement->firstSourceLocation(), tr("Expected array of strings after colon.")); + return; + } + + ArrayPattern *arrayLit = AST::cast<ArrayPattern *>(expStmt->expression); + if (!arrayLit) { + addError(expStmt->firstSourceLocation(), tr("Expected array of strings after colon.")); + return; + } + + for (PatternElementList *it = arrayLit->elements; it; it = it->next) { + StringLiteral *stringLit = AST::cast<StringLiteral *>(it->element->initializer); + if (!stringLit) { + addError(arrayLit->firstSourceLocation(), tr("Expected array literal with only string literal members.")); + return; + } + QString exp = stringLit->value.toString(); + int slashIdx = exp.indexOf(QLatin1Char('/')); + int spaceIdx = exp.indexOf(QLatin1Char(' ')); + ComponentVersion version(exp.mid(spaceIdx + 1)); + + if (spaceIdx == -1 || !version.isValid()) { + addError(stringLit->firstSourceLocation(), tr("Expected string literal to contain 'Package/Name major.minor' or 'Name major.minor'.")); + continue; + } + QString package; + if (slashIdx != -1) + package = exp.left(slashIdx); + QString name = exp.mid(slashIdx + 1, spaceIdx - (slashIdx+1)); + + // ### relocatable exports where package is empty? + fmo->addExport(name, package, version); + } +} + +void TypeDescriptionReader::readMetaObjectRevisions(UiScriptBinding *ast, FakeMetaObject::Ptr fmo) +{ + QTC_ASSERT(ast, return); + + if (!ast->statement) { + addError(ast->colonToken, tr("Expected array of numbers after colon.")); + return; + } + + ExpressionStatement *expStmt = AST::cast<ExpressionStatement *>(ast->statement); + if (!expStmt) { + addError(ast->statement->firstSourceLocation(), tr("Expected array of numbers after colon.")); + return; + } + + ArrayPattern *arrayLit = AST::cast<ArrayPattern *>(expStmt->expression); + if (!arrayLit) { + addError(expStmt->firstSourceLocation(), tr("Expected array of numbers after colon.")); + return; + } + + int exportIndex = 0; + const int exportCount = fmo->exports().size(); + for (PatternElementList *it = arrayLit->elements; it; it = it->next, ++exportIndex) { + NumericLiteral *numberLit = cast<NumericLiteral *>(it->element->initializer); + if (!numberLit) { + addError(arrayLit->firstSourceLocation(), tr("Expected array literal with only number literal members.")); + return; + } + + if (exportIndex >= exportCount) { + addError(numberLit->firstSourceLocation(), tr("Meta object revision without matching export.")); + return; + } + + const double v = numberLit->value; + const int metaObjectRevision = static_cast<int>(v); + if (metaObjectRevision != v) { + addError(numberLit->firstSourceLocation(), tr("Expected integer.")); + return; + } + + fmo->setExportMetaObjectRevision(exportIndex, metaObjectRevision); + } +} + +void TypeDescriptionReader::readEnumValues(AST::UiScriptBinding *ast, LanguageUtils::FakeMetaEnum *fme) +{ + if (!ast) + return; + if (!ast->statement) { + addError(ast->colonToken, tr("Expected object literal after colon.")); + return; + } + + ExpressionStatement *expStmt = AST::cast<ExpressionStatement *>(ast->statement); + if (!expStmt) { + addError(ast->statement->firstSourceLocation(), tr("Expected object literal after colon.")); + return; + } + + ObjectPattern *objectLit = AST::cast<ObjectPattern *>(expStmt->expression); + if (!objectLit) { + addError(expStmt->firstSourceLocation(), tr("Expected object literal after colon.")); + return; + } + + for (PatternPropertyList *it = objectLit->properties; it; it = it->next) { + PatternProperty *assignement = AST::cast<PatternProperty *>(it->property); + if (assignement) { + StringLiteralPropertyName *propName = AST::cast<StringLiteralPropertyName *>(assignement->name); + NumericLiteral *value = AST::cast<NumericLiteral *>(assignement->initializer); + UnaryMinusExpression *minus = AST::cast<UnaryMinusExpression *>(assignement->initializer); + if (minus) + value = AST::cast<NumericLiteral *>(minus->expression); + if (!propName || !value) { + addError(objectLit->firstSourceLocation(), tr("Expected object literal to contain only 'string: number' elements.")); + continue; + } + + double v = value->value; + if (minus) + v = -v; + fme->addKey(propName->id.toString(), v); + continue; + } + PatternPropertyList *getterSetter = AST::cast<PatternPropertyList *>(it->next); + if (getterSetter) + addError(objectLit->firstSourceLocation(), tr("Enum should not contain getter and setters, but only 'string: number' elements.")); + } +} diff --git a/tools/qmllint/qmljstypedescriptionreader.h b/tools/qmllint/qmljstypedescriptionreader.h new file mode 100644 index 0000000000..df215af8d2 --- /dev/null +++ b/tools/qmllint/qmljstypedescriptionreader.h @@ -0,0 +1,103 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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 QMLJSTYPEDESCRIPTIONREADER_H +#define QMLJSTYPEDESCRIPTIONREADER_H + +#include <private/qqmljsastfwd_p.h> +#include "fakemetaobject.h" + +// for Q_DECLARE_TR_FUNCTIONS +#include <QCoreApplication> + +QT_BEGIN_NAMESPACE +class QIODevice; +class QBuffer; + +namespace QQmlJS { + +class ModuleApiInfo +{ +public: + QString uri; + LanguageUtils::ComponentVersion version; + QString cppName; +}; + + +class TypeDescriptionReader +{ + Q_DECLARE_TR_FUNCTIONS(QQmlJS::TypeDescriptionReader) + +public: + explicit TypeDescriptionReader(const QString &fileName, const QString &data); + ~TypeDescriptionReader(); + + bool operator()( + QHash<QString, LanguageUtils::FakeMetaObject::ConstPtr> *objects, + QList<ModuleApiInfo> *moduleApis, + QStringList *dependencies); + QString errorMessage() const; + QString warningMessage() const; + +private: + void readDocument(AST::UiProgram *ast); + void readModule(AST::UiObjectDefinition *ast); + void readDependencies(AST::UiScriptBinding *ast); + void readComponent(AST::UiObjectDefinition *ast); + void readModuleApi(AST::UiObjectDefinition *ast); + void readSignalOrMethod(AST::UiObjectDefinition *ast, bool isMethod, LanguageUtils::FakeMetaObject::Ptr fmo); + void readProperty(AST::UiObjectDefinition *ast, LanguageUtils::FakeMetaObject::Ptr fmo); + void readEnum(AST::UiObjectDefinition *ast, LanguageUtils::FakeMetaObject::Ptr fmo); + void readParameter(AST::UiObjectDefinition *ast, LanguageUtils::FakeMetaMethod *fmm); + + QString readStringBinding(AST::UiScriptBinding *ast); + bool readBoolBinding(AST::UiScriptBinding *ast); + double readNumericBinding(AST::UiScriptBinding *ast); + LanguageUtils::ComponentVersion readNumericVersionBinding(AST::UiScriptBinding *ast); + int readIntBinding(AST::UiScriptBinding *ast); + void readExports(AST::UiScriptBinding *ast, LanguageUtils::FakeMetaObject::Ptr fmo); + void readMetaObjectRevisions(AST::UiScriptBinding *ast, LanguageUtils::FakeMetaObject::Ptr fmo); + void readEnumValues(AST::UiScriptBinding *ast, LanguageUtils::FakeMetaEnum *fme); + + void addError(const AST::SourceLocation &loc, const QString &message); + void addWarning(const AST::SourceLocation &loc, const QString &message); + + QString _fileName; + QString _source; + QString _errorMessage; + QString _warningMessage; + QHash<QString, LanguageUtils::FakeMetaObject::ConstPtr> *_objects; + QList<ModuleApiInfo> *_moduleApis = nullptr; + QStringList *_dependencies = nullptr; +}; + +} // namespace QQmlJS +QT_END_NAMESPACE + +#endif // QMLJSTYPEDESCRIPTIONREADER_H diff --git a/tools/qmllint/qmllint.pro b/tools/qmllint/qmllint.pro index 91ab2f8afc..76363a7cd8 100644 --- a/tools/qmllint/qmllint.pro +++ b/tools/qmllint/qmllint.pro @@ -2,8 +2,22 @@ option(host_build) QT = core qmldevtools-private -SOURCES += main.cpp +SOURCES += main.cpp \ + componentversion.cpp \ + fakemetaobject.cpp \ + findunqualified.cpp \ + qmljstypedescriptionreader.cpp \ + qcoloroutput.cpp \ + scopetree.cpp QMAKE_TARGET_DESCRIPTION = QML Syntax Verifier load(qt_tool) + +HEADERS += \ + componentversion.h \ + fakemetaobject.h \ + findunqualified.h \ + qmljstypedescriptionreader.h \ + qcoloroutput_p.h \ + scopetree.h diff --git a/tools/qmllint/scopetree.cpp b/tools/qmllint/scopetree.cpp new file mode 100644 index 0000000000..2eff3fa319 --- /dev/null +++ b/tools/qmllint/scopetree.cpp @@ -0,0 +1,269 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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 "scopetree.h" + +#include "qcoloroutput_p.h" + +#include <algorithm> + +#include <QQueue> + +ScopeTree::ScopeTree(ScopeType type, QString name, ScopeTree *parentScope) + : m_parentScope(parentScope), m_name(name), m_scopeType(type) {} + +ScopeTree *ScopeTree::createNewChildScope(ScopeType type, QString name) { + Q_ASSERT(type != ScopeType::QMLScope|| !m_parentScope || m_parentScope->m_scopeType == ScopeType::QMLScope || m_parentScope->m_name == "global"); + auto childScope = new ScopeTree{type, name, this}; + m_childScopes.push_back(childScope); + return childScope; +} + +ScopeTree *ScopeTree::parentScope() { + return m_parentScope; +} + +void ScopeTree::insertJSIdentifier(QString id, QQmlJS::AST::VariableScope scope) +{ + Q_ASSERT(m_scopeType != ScopeType::QMLScope); + if (scope == QQmlJS::AST::VariableScope::Var) { + auto targetScope = this; + while (targetScope->scopeType() != ScopeType::JSFunctionScope) { + targetScope = targetScope->m_parentScope; + } + targetScope->m_currentScopeJSIdentifiers.insert(id); + } else { + m_currentScopeJSIdentifiers.insert(id); + } +} + +void ScopeTree::insertQMLIdentifier(QString id) +{ + Q_ASSERT(m_scopeType == ScopeType::QMLScope); + m_currentScopeQMLIdentifiers.insert(id); +} + +void ScopeTree::insertSignalIdentifier(QString id, LanguageUtils::FakeMetaMethod method, QQmlJS::AST::SourceLocation loc, bool hasMultilineHandlerBody) +{ + Q_ASSERT(m_scopeType == ScopeType::QMLScope); + m_injectedSignalIdentifiers.insert(id, {method, loc, hasMultilineHandlerBody}); +} + +void ScopeTree::insertPropertyIdentifier(QString id) +{ + this->insertQMLIdentifier(id); + LanguageUtils::FakeMetaMethod method( id + QLatin1String("Changed"), "void"); + this->addMethod(method); +} + +bool ScopeTree::isIdInCurrentScope(const QString &id) const +{ + return isIdInCurrentQMlScopes(id) || isIdInCurrentJSScopes(id); +} + +void ScopeTree::addIdToAccssedIfNotInParentScopes(const QPair<QString, QQmlJS::AST::SourceLocation> &id_loc_pair, const QSet<QString>& unknownImports) { + // also do not add id if it is parent + // parent is almost always defined valid in QML, and if we could not find a definition for the current QML component + // not skipping "parent" will lead to many false positives + // Moreover, if the top level item is Item or inherits from it, it will have a parent property to which we would point the user + // which makes for a very nonsensical warning + auto qmlScope = getCurrentQMLScope(); + if (!isIdInCurrentScope(id_loc_pair.first) && !(id_loc_pair.first == QLatin1String("parent") && qmlScope && unknownImports.contains(qmlScope->name()))) { + m_accessedIdentifiers.push_back(id_loc_pair); + } +} + +bool ScopeTree::isVisualRootScope() const +{ + return m_parentScope && m_parentScope->m_parentScope && m_parentScope->m_parentScope->m_parentScope == nullptr; +} + +QString ScopeTree::name() const +{ + return m_name; +} + +struct IssueLocationWithContext +{ + IssueLocationWithContext(const QString& code, QQmlJS::AST::SourceLocation location) { + int before = std::max(0,code.lastIndexOf('\n', location.offset)); + beforeText = code.midRef(before+1, location.offset - (before+1) ); + issueText = code.midRef(location.offset, location.length); + int after = code.indexOf('\n', location.offset + location.length); + afterText = code.midRef(location.offset+location.length, after - (location.offset+location.length)); + } + + QStringRef beforeText; + QStringRef issueText; + QStringRef afterText; +}; + +bool ScopeTree::recheckIdentifiers(const QString& code, const QHash<QString, LanguageUtils::FakeMetaObject::ConstPtr> &qmlIDs, const ScopeTree *root, const QString& rootId, ColorOutput& colorOut) const +{ + bool noUnqualifiedIdentifier = true; + + // revisit all scopes + QQueue<const ScopeTree*> workQueue; + workQueue.enqueue(this); + while (!workQueue.empty()) { + const ScopeTree* currentScope = workQueue.dequeue(); + for (auto idLocationPair : currentScope->m_accessedIdentifiers) { + if (qmlIDs.contains(idLocationPair.first)) + continue; + if (currentScope->isIdInCurrentScope(idLocationPair.first)) { + continue; + } + noUnqualifiedIdentifier = false; + colorOut.write("Warning: ", Warning); + auto location = idLocationPair.second; + colorOut.write(QString::asprintf("unqualified access at %d:%d\n", location.startLine, location.startColumn), Normal); + IssueLocationWithContext issueLocationWithContext {code, location}; + colorOut.write(issueLocationWithContext.beforeText.toString(), Normal); + colorOut.write(issueLocationWithContext.issueText.toString(), Error); + colorOut.write(issueLocationWithContext.afterText.toString() + QLatin1Char('\n'), Normal); + int tabCount = issueLocationWithContext.beforeText.count(QLatin1Char('\t')); + colorOut.write(QString(" ").repeated(issueLocationWithContext.beforeText.length() - tabCount) + QString("\t").repeated(tabCount) + QString("^").repeated(location.length) + QLatin1Char('\n'), Normal); + // root(JS) --> program(qml) --> (first element) + if (root->m_childScopes[0]->m_childScopes[0]->m_currentScopeQMLIdentifiers.contains(idLocationPair.first)) { + ScopeTree *parentScope = currentScope->m_parentScope; + while (parentScope && parentScope->scopeType() != ScopeType::QMLScope) { + parentScope = parentScope->m_parentScope; + } + colorOut.write("Note: ", Info); + colorOut.write( idLocationPair.first + QLatin1String(" is a meber of the root element\n"), Normal ); + colorOut.write(QLatin1String(" You can qualify the access with its id to avoid this warning:\n"), Normal); + if (rootId == QLatin1String("<id>")) { + colorOut.write("Note: ", Warning); + colorOut.write(("You first have to give the root element an id\n")); + } + colorOut.write(issueLocationWithContext.beforeText.toString(), Normal); + colorOut.write(rootId + QLatin1Char('.'), Hint); + colorOut.write(issueLocationWithContext.issueText.toString(), Normal); + colorOut.write(issueLocationWithContext.afterText + QLatin1Char('\n'), Normal); + } else if (currentScope->isIdInjectedFromSignal(idLocationPair.first)) { + auto qmlScope = currentScope->getCurrentQMLScope(); + auto methodUsages = qmlScope->m_injectedSignalIdentifiers.values(idLocationPair.first); + auto location = idLocationPair.second; + // sort the list of signal handlers by their occurrence in the source code + // then, we select the first one whose location is after the unqualified id + // and go one step backwards to get the one which we actually need + std::sort(methodUsages.begin(), methodUsages.end(), [](const MethodUsage m1, const MethodUsage m2) { + return m1.loc.startLine < m2.loc.startLine || (m1.loc.startLine == m2.loc.startLine && m1.loc.startColumn < m2.loc.startColumn); + }); + auto oneBehindIt = std::find_if(methodUsages.begin(), methodUsages.end(), [&location](MethodUsage methodUsage) { + return location.startLine < methodUsage.loc.startLine || (location.startLine == methodUsage.loc.startLine && location.startColumn < methodUsage.loc.startColumn); + }); + auto methodUsage = *(--oneBehindIt); + colorOut.write("Note:", Info); + colorOut.write(idLocationPair.first + QString::asprintf(" is accessible in this scope because you are handling a signal at %d:%d\n", methodUsage.loc.startLine, methodUsage.loc.startColumn), Normal); + colorOut.write("Consider using a function instead\n", Normal); + IssueLocationWithContext context {code, methodUsage.loc}; + colorOut.write(context.beforeText + QLatin1Char(' ')); + colorOut.write(methodUsage.hasMultilineHandlerBody ? "function(" : "(", Hint); + const auto parameters = methodUsage.method.parameterNames(); + for (int numParams = parameters.size(); numParams > 0; --numParams) { + colorOut.write(parameters.at(parameters.size() - numParams), Hint); + if (numParams > 1) { + colorOut.write(", ", Hint); + } + } + colorOut.write(methodUsage.hasMultilineHandlerBody ? ")" : ") => ", Hint); + colorOut.write(" {...", Normal); + } + colorOut.write("\n\n\n", Normal); + } + for (auto const& childScope: currentScope->m_childScopes) { + workQueue.enqueue(childScope); + } + } + return noUnqualifiedIdentifier; +} + +QMap<QString, LanguageUtils::FakeMetaMethod>const &ScopeTree::methods() const +{ + return m_methods; +} + +bool ScopeTree::isIdInCurrentQMlScopes(QString id) const +{ + auto qmlScope = getCurrentQMLScope(); + return qmlScope->m_currentScopeQMLIdentifiers.contains(id); +} + +bool ScopeTree::isIdInCurrentJSScopes(QString id) const +{ + auto jsScope = this; + while (jsScope) { + if (jsScope->m_scopeType != ScopeType::QMLScope && jsScope->m_currentScopeJSIdentifiers.contains(id)) + return true; + jsScope = jsScope->m_parentScope; + } + return false; +} + +bool ScopeTree::isIdInjectedFromSignal(QString id) const +{ + auto qmlScope = getCurrentQMLScope(); + return qmlScope->m_injectedSignalIdentifiers.contains(id); +} + +const ScopeTree *ScopeTree::getCurrentQMLScope() const +{ + auto qmlScope = this; + while (qmlScope && qmlScope->m_scopeType != ScopeType::QMLScope) { + qmlScope = qmlScope->m_parentScope; + } + return qmlScope; +} + +ScopeTree *ScopeTree::getCurrentQMLScope() +{ + auto qmlScope = this; + while (qmlScope && qmlScope->m_scopeType != ScopeType::QMLScope) { + qmlScope = qmlScope->m_parentScope; + } + return qmlScope; +} + +ScopeType ScopeTree::scopeType() {return m_scopeType;} + +void ScopeTree::addMethod(LanguageUtils::FakeMetaMethod method) +{ + m_methods.insert(method.methodName(), method); +} + +void ScopeTree::addMethodsFromMetaObject(LanguageUtils::FakeMetaObject::ConstPtr metaObject) +{ + if (metaObject) { + auto methodCount = metaObject->methodCount(); + for (auto i = 0; i < methodCount; ++i) { + auto method = metaObject->method(i); + this->addMethod(method); + } + } +} diff --git a/tools/qmllint/scopetree.h b/tools/qmllint/scopetree.h new file mode 100644 index 0000000000..872a509123 --- /dev/null +++ b/tools/qmllint/scopetree.h @@ -0,0 +1,106 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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 SCOPETREE_H +#define SCOPETREE_H + +#include "fakemetaobject.h" +#include "private/qqmljsast_p.h" +#include "private/qqmljssourcelocation_p.h" + +#include <QSet> +#include <QString> +#include <QMap> + +enum MessageColors{ + Error, + Warning, + Info, + Normal, + Hint +}; + +enum class ScopeType +{ + JSFunctionScope, + JSLexicalScope, + QMLScope +}; + +struct MethodUsage +{ + LanguageUtils::FakeMetaMethod method; + QQmlJS::AST::SourceLocation loc; + bool hasMultilineHandlerBody; +}; + +class ColorOutput; + +class ScopeTree { +public: + ScopeTree(ScopeType type, QString name="<none given>", ScopeTree* parentScope=nullptr); + ~ScopeTree() {qDeleteAll(m_childScopes);} + + ScopeTree* createNewChildScope(ScopeType type, QString name); + ScopeTree* parentScope(); + + void insertJSIdentifier(QString id, QQmlJS::AST::VariableScope scope); + void insertQMLIdentifier(QString id); + void insertSignalIdentifier(QString id, LanguageUtils::FakeMetaMethod method, QQmlJS::AST::SourceLocation loc, bool hasMultilineHandlerBody); + void insertPropertyIdentifier(QString id); // inserts property as qml identifier as well as the corresponding + + bool isIdInCurrentScope(QString const &id) const; + void addIdToAccssedIfNotInParentScopes(QPair<QString, QQmlJS::AST::SourceLocation> const& id_loc_pair, const QSet<QString>& unknownImports); + + bool isVisualRootScope() const; + QString name() const; + + bool recheckIdentifiers(const QString &code, const QHash<QString, LanguageUtils::FakeMetaObject::ConstPtr>& qmlIDs, const ScopeTree *root, const QString& rootId, ColorOutput &colorOut) const; + ScopeType scopeType(); + void addMethod(LanguageUtils::FakeMetaMethod); + void addMethodsFromMetaObject(LanguageUtils::FakeMetaObject::ConstPtr metaObject); + QMap<QString, LanguageUtils::FakeMetaMethod>const & methods() const; + +private: + QSet<QString> m_currentScopeJSIdentifiers; + QSet<QString> m_currentScopeQMLIdentifiers; + QMultiHash<QString, MethodUsage> m_injectedSignalIdentifiers; + QMap<QString, LanguageUtils::FakeMetaMethod> m_methods; + QVector<QPair<QString, QQmlJS::AST::SourceLocation>> m_accessedIdentifiers; + QVector<ScopeTree*> m_childScopes; + ScopeTree *m_parentScope; + QString m_name; + ScopeType m_scopeType; + + bool isIdInCurrentQMlScopes(QString id) const; + bool isIdInCurrentJSScopes(QString id) const; + bool isIdInjectedFromSignal(QString id) const; + const ScopeTree* getCurrentQMLScope() const; + ScopeTree* getCurrentQMLScope(); +}; +#endif // SCOPETREE_H diff --git a/tools/qmlplugindump/main.cpp b/tools/qmlplugindump/main.cpp index 0a66aa419b..1556718471 100644 --- a/tools/qmlplugindump/main.cpp +++ b/tools/qmlplugindump/main.cpp @@ -100,21 +100,51 @@ static QString enquote(const QString &string) .replace(QLatin1Char('"'),QLatin1String("\\\""))); } -void collectReachableMetaObjects(const QMetaObject *meta, QSet<const QMetaObject *> *metas, bool extended = false) +struct QmlVersionInfo { + QString pluginImportUri; + int majorVersion; + int minorVersion; + bool strict; +}; + +static bool matchingImportUri(const QQmlType &ty, const QmlVersionInfo& versionInfo) { + if (versionInfo.strict) { + return (versionInfo.pluginImportUri == ty.module() + && (ty.majorVersion() == versionInfo.majorVersion || ty.majorVersion() == -1)) + || ty.module().isEmpty(); + } + return ty.module().isEmpty() + || versionInfo.pluginImportUri == ty.module() + || ty.module().startsWith(versionInfo.pluginImportUri + QLatin1Char('.')); +} + +void collectReachableMetaObjects(const QMetaObject *meta, QSet<const QMetaObject *> *metas, const QmlVersionInfo &info, bool extended = false, bool alreadyChangedModule = false) +{ + auto ty = QQmlMetaType::qmlType(meta); if (! meta || metas->contains(meta)) return; - // dynamic meta objects can break things badly - // but extended types are usually fine - const QMetaObjectPrivate *mop = reinterpret_cast<const QMetaObjectPrivate *>(meta->d.data); - if (extended || !(mop->flags & DynamicMetaObject)) - metas->insert(meta); + if (matchingImportUri(ty, info)) { + if (!alreadyChangedModule) { + // dynamic meta objects can break things badly + // but extended types are usually fine + const QMetaObjectPrivate *mop = reinterpret_cast<const QMetaObjectPrivate *>(meta->d.data); + if (extended || !(mop->flags & DynamicMetaObject)) + metas->insert(meta); + } else if (!ty.module().isEmpty()) { // empty module (e.g. from an attached property) would cause a (false) match; do not warn about them + qWarning() << "Circular module dependency cannot be expressed in plugin.qmltypes file" + << "Object was:" << meta->className() + << ty.module() << info.pluginImportUri; + } + } else if (!ty.module().isEmpty()) { + alreadyChangedModule = true; + } - collectReachableMetaObjects(meta->superClass(), metas); + collectReachableMetaObjects(meta->superClass(), metas, info, /*extended=*/ false, alreadyChangedModule); } -void collectReachableMetaObjects(QObject *object, QSet<const QMetaObject *> *metas) +void collectReachableMetaObjects(QObject *object, QSet<const QMetaObject *> *metas, const QmlVersionInfo &info) { if (! object) return; @@ -122,7 +152,7 @@ void collectReachableMetaObjects(QObject *object, QSet<const QMetaObject *> *met const QMetaObject *meta = object->metaObject(); if (verbose) std::cerr << "Processing object " << qPrintable( meta->className() ) << std::endl; - collectReachableMetaObjects(meta, metas); + collectReachableMetaObjects(meta, metas, info); for (int index = 0; index < meta->propertyCount(); ++index) { QMetaProperty prop = meta->property(index); @@ -135,17 +165,18 @@ void collectReachableMetaObjects(QObject *object, QSet<const QMetaObject *> *met // accessing a member of oo is going to cause a segmentation fault QObject *oo = QQmlMetaType::toQObject(prop.read(object)); if (oo && !metas->contains(oo->metaObject())) - collectReachableMetaObjects(oo, metas); + collectReachableMetaObjects(oo, metas, info); currentProperty.clear(); } } } -void collectReachableMetaObjects(QQmlEnginePrivate *engine, const QQmlType &ty, QSet<const QMetaObject *> *metas) +void collectReachableMetaObjects(QQmlEnginePrivate *engine, const QQmlType &ty, QSet<const QMetaObject *> *metas, const QmlVersionInfo& info) { - collectReachableMetaObjects(ty.baseMetaObject(), metas, ty.isExtendedType()); - if (ty.attachedPropertiesType(engine)) - collectReachableMetaObjects(ty.attachedPropertiesType(engine), metas); + collectReachableMetaObjects(ty.baseMetaObject(), metas, info, ty.isExtendedType()); + if (ty.attachedPropertiesType(engine) && matchingImportUri(ty, info)) { + collectReachableMetaObjects(ty.attachedPropertiesType(engine), metas, info); + } } /* We want to add the MetaObject for 'Qt' to the list, this is a @@ -205,14 +236,14 @@ QByteArray convertToId(const QMetaObject *mo) // Collect all metaobjects for types registered with qmlRegisterType() without parameters void collectReachableMetaObjectsWithoutQmlName(QQmlEnginePrivate *engine, QSet<const QMetaObject *>& metas, - QMap<QString, QSet<QQmlType>> &compositeTypes) { + QMap<QString, QList<QQmlType>> &compositeTypes, const QmlVersionInfo &info) { const auto qmlAllTypes = QQmlMetaType::qmlAllTypes(); for (const QQmlType &ty : qmlAllTypes) { if (!metas.contains(ty.baseMetaObject())) { if (!ty.isComposite()) { - collectReachableMetaObjects(engine, ty, &metas); - } else { - compositeTypes[ty.elementName()].insert(ty); + collectReachableMetaObjects(engine, ty, &metas, info); + } else if (matchingImportUri(ty, info)) { + compositeTypes[ty.elementName()].append(ty); } } } @@ -221,23 +252,27 @@ void collectReachableMetaObjectsWithoutQmlName(QQmlEnginePrivate *engine, QSet<c QSet<const QMetaObject *> collectReachableMetaObjects(QQmlEngine *engine, QSet<const QMetaObject *> &noncreatables, QSet<const QMetaObject *> &singletons, - QMap<QString, QSet<QQmlType>> &compositeTypes, - const QList<QQmlType> &skip = QList<QQmlType>()) + QMap<QString, QList<QQmlType>> &compositeTypes, + const QmlVersionInfo &info, + const QList<QQmlType> &skip = QList<QQmlType>() + ) { QSet<const QMetaObject *> metas; metas.insert(FriendlyQObject::qtMeta()); const auto qmlTypes = QQmlMetaType::qmlTypes(); for (const QQmlType &ty : qmlTypes) { + if (!matchingImportUri(ty,info)) + continue; if (!ty.isCreatable()) noncreatables.insert(ty.baseMetaObject()); if (ty.isSingleton()) singletons.insert(ty.baseMetaObject()); if (!ty.isComposite()) { qmlTypesByCppName[ty.baseMetaObject()->className()].insert(ty); - collectReachableMetaObjects(QQmlEnginePrivate::get(engine), ty, &metas); + collectReachableMetaObjects(QQmlEnginePrivate::get(engine), ty, &metas, info); } else { - compositeTypes[ty.elementName()].insert(ty); + compositeTypes[ty.elementName()].append(ty); } } @@ -245,6 +280,8 @@ QSet<const QMetaObject *> collectReachableMetaObjects(QQmlEngine *engine, // find even more QMetaObjects by instantiating QML types and running // over the instances for (const QQmlType &ty : qmlTypes) { + if (!matchingImportUri(ty, info)) + continue; if (skip.contains(ty)) continue; if (ty.isExtendedType()) @@ -270,13 +307,12 @@ QSet<const QMetaObject *> collectReachableMetaObjects(QQmlEngine *engine, << " is singleton, but has no singletonInstanceInfo" << std::endl; continue; } - if (siinfo->qobjectCallback) { + if (ty.isQObjectSingleton()) { if (verbose) std::cerr << "Trying to get singleton for " << qPrintable(tyName) << " (" << qPrintable( siinfo->typeName ) << ")" << std::endl; - siinfo->init(engine); - collectReachableMetaObjects(object, &metas); - object = siinfo->qobjectApi(engine); + collectReachableMetaObjects(object, &metas, info); + object = QQmlEnginePrivate::get(engine)->singletonInstance<QObject*>(ty); } else { inObjectInstantiation.clear(); continue; // we don't handle QJSValue singleton types. @@ -294,7 +330,7 @@ QSet<const QMetaObject *> collectReachableMetaObjects(QQmlEngine *engine, if (verbose) std::cerr << "Got " << qPrintable( tyName ) << " (" << qPrintable( QString::fromUtf8(ty.typeName()) ) << ")" << std::endl; - collectReachableMetaObjects(object, &metas); + collectReachableMetaObjects(object, &metas, info); object->deleteLater(); } else { std::cerr << "Could not create " << qPrintable(tyName) << std::endl; @@ -302,7 +338,7 @@ QSet<const QMetaObject *> collectReachableMetaObjects(QQmlEngine *engine, } } - collectReachableMetaObjectsWithoutQmlName(QQmlEnginePrivate::get(engine), metas, compositeTypes); + collectReachableMetaObjectsWithoutQmlName(QQmlEnginePrivate::get(engine), metas, compositeTypes, info); return metas; } @@ -344,23 +380,21 @@ public: relocatableModuleUri = uri; } - const QString getExportString(QString qmlTyName, int majorVersion, int minorVersion) + QString getExportString(const QQmlType &type, const QmlVersionInfo &versionInfo) { - if (qmlTyName.startsWith(relocatableModuleUri + QLatin1Char('/'))) { - qmlTyName.remove(0, relocatableModuleUri.size() + 1); - } - if (qmlTyName.startsWith("./")) { - qmlTyName.remove(0, 2); - } - if (qmlTyName.startsWith(QLatin1Char('/'))) { - qmlTyName.remove(0, 1); - } - const QString exportString = enquote( - QString("%1 %2.%3").arg( - qmlTyName, - QString::number(majorVersion), - QString::number(minorVersion))); - return exportString; + const QString module = type.module().isEmpty() ? versionInfo.pluginImportUri + : type.module(); + const int majorVersion = type.majorVersion() >= 0 ? type.majorVersion() + : versionInfo.majorVersion; + const int minorVersion = type.minorVersion() >= 0 ? type.minorVersion() + : versionInfo.minorVersion; + + const QString versionedElement = type.elementName() + + QString::fromLatin1(" %1.%2").arg(majorVersion).arg(minorVersion); + + return enquote((module == relocatableModuleUri) + ? versionedElement + : module + QLatin1Char('/') + versionedElement); } void writeMetaContent(const QMetaObject *meta, KnownAttributes *knownAttributes = nullptr) @@ -405,11 +439,13 @@ public: } } - QString getPrototypeNameForCompositeType(const QMetaObject *metaObject, QSet<QByteArray> &defaultReachableNames, - QList<const QMetaObject *> *objectsToMerge) + QString getPrototypeNameForCompositeType( + const QMetaObject *metaObject, QList<const QMetaObject *> *objectsToMerge, + const QmlVersionInfo &versionInfo) { + auto ty = QQmlMetaType::qmlType(metaObject); QString prototypeName; - if (!defaultReachableNames.contains(metaObject->className())) { + if (matchingImportUri(ty, versionInfo)) { // dynamic meta objects can break things badly // but extended types are usually fine const QMetaObjectPrivate *mop = reinterpret_cast<const QMetaObjectPrivate *>(metaObject->d.data); @@ -417,24 +453,28 @@ public: && !objectsToMerge->contains(metaObject)) objectsToMerge->append(metaObject); const QMetaObject *superMetaObject = metaObject->superClass(); - if (!superMetaObject) + if (!superMetaObject) { prototypeName = "QObject"; - else + } else { + QQmlType superType = QQmlMetaType::qmlType(superMetaObject); + if (superType.isValid() && !superType.isComposite()) + return convertToId(superMetaObject->className()); prototypeName = getPrototypeNameForCompositeType( - superMetaObject, defaultReachableNames, objectsToMerge); + superMetaObject, objectsToMerge, versionInfo); + } } else { prototypeName = convertToId(metaObject->className()); } return prototypeName; } - void dumpComposite(QQmlEngine *engine, const QSet<QQmlType> &compositeType, QSet<QByteArray> &defaultReachableNames) + void dumpComposite(QQmlEngine *engine, const QList<QQmlType> &compositeType, const QmlVersionInfo &versionInfo) { for (const QQmlType &type : compositeType) - dumpCompositeItem(engine, type, defaultReachableNames); + dumpCompositeItem(engine, type, versionInfo); } - void dumpCompositeItem(QQmlEngine *engine, const QQmlType &compositeType, QSet<QByteArray> &defaultReachableNames) + void dumpCompositeItem(QQmlEngine *engine, const QQmlType &compositeType, const QmlVersionInfo &versionInfo) { QQmlComponent e(engine, compositeType.sourceUrl()); if (!e.isReady()) { @@ -455,13 +495,17 @@ public: QList<const QMetaObject *> objectsToMerge; KnownAttributes knownAttributes; // Get C++ base class name for the composite type - QString prototypeName = getPrototypeNameForCompositeType(mainMeta, defaultReachableNames, - &objectsToMerge); + QString prototypeName = getPrototypeNameForCompositeType(mainMeta, &objectsToMerge, + versionInfo); qml->writeScriptBinding(QLatin1String("prototype"), enquote(prototypeName)); QString qmlTyName = compositeType.qmlTypeName(); - const QString exportString = getExportString(qmlTyName, compositeType.majorVersion(), compositeType.minorVersion()); + const QString exportString = getExportString(compositeType, versionInfo); + + // TODO: why don't we simply output the compositeType.elementName() here? + // That would make more sense, but it would change the format quite a bit. qml->writeScriptBinding(QLatin1String("name"), exportString); + qml->writeArrayBinding(QLatin1String("exports"), QStringList() << exportString); qml->writeArrayBinding(QLatin1String("exportMetaObjectRevisions"), QStringList() << QString::number(compositeType.minorVersion())); qml->writeBooleanBinding(QLatin1String("isComposite"), true); @@ -528,7 +572,7 @@ public: if (attachedType != meta) attachedTypeId = convertToId(attachedType); } - const QString exportString = getExportString(type.qmlTypeName(), type.majorVersion(), type.minorVersion()); + const QString exportString = getExportString(type, { QString(), -1, -1, false }); int metaObjectRevision = type.metaObjectRevision(); if (extendedObject) { // emulate custom metaobjectrevision out of import @@ -961,6 +1005,16 @@ void printDebugMessage(QtMsgType, const QMessageLogContext &, const QString &msg // In case of QtFatalMsg the calling code will abort() when appropriate. } +QT_BEGIN_NAMESPACE +static bool operator<(const QQmlType &a, const QQmlType &b) +{ + return a.qmlTypeName() < b.qmlTypeName() + || (a.qmlTypeName() == b.qmlTypeName() + && ((a.majorVersion() < b.majorVersion()) + || (a.majorVersion() == b.majorVersion() + && a.minorVersion() < b.minorVersion()))); +} +QT_END_NAMESPACE int main(int argc, char *argv[]) { @@ -1026,6 +1080,7 @@ int main(int argc, char *argv[]) QString dependenciesFile; QString mergeFile; bool forceQtQuickDependency = true; + bool strict = false; enum Action { Uri, Path, Builtins }; Action action = Uri; { @@ -1090,6 +1145,10 @@ int main(int argc, char *argv[]) } else if (arg == QLatin1String("--qapp") || arg == QLatin1String("-qapp")) { continue; + } else if (arg == QLatin1String("--strict") + || arg == QLatin1String("-strict")) { + strict = true; + continue; } else { std::cerr << "Invalid argument: " << qPrintable(arg) << std::endl; return EXIT_INVALIDARGUMENTS; @@ -1186,50 +1245,34 @@ int main(int argc, char *argv[]) // find all QMetaObjects reachable from the builtin module QSet<const QMetaObject *> uncreatableMetas; QSet<const QMetaObject *> singletonMetas; - QMap<QString, QSet<QQmlType>> defaultCompositeTypes; - QSet<const QMetaObject *> defaultReachable = collectReachableMetaObjects(&engine, uncreatableMetas, singletonMetas, defaultCompositeTypes); - QList<QQmlType> defaultTypes = QQmlMetaType::qmlTypes(); - - // add some otherwise unreachable QMetaObjects - defaultReachable.insert(&QQuickMouseEvent::staticMetaObject); - // QQuickKeyEvent, QQuickPinchEvent, QQuickDropEvent are not exported - QSet<QByteArray> defaultReachableNames; // this will hold the meta objects we want to dump information of QSet<const QMetaObject *> metas; // composite types we want to dump information of - QMap<QString, QSet<QQmlType>> compositeTypes; + QMap<QString, QList<QQmlType>> compositeTypes; + int majorVersion = qtQmlMajorVersion, minorVersion = qtQmlMinorVersion; + QmlVersionInfo info; if (action == Builtins) { - for (const QMetaObject *m : qAsConst(defaultReachable)) { - if (m->className() == QLatin1String("Qt")) { - metas.insert(m); - break; - } - } - } else if (pluginImportUri == QLatin1String("QtQml")) { - bool ok = false; - const uint major = pluginImportVersion.splitRef('.').at(0).toUInt(&ok, 10); - if (!ok) { - std::cerr << "Malformed version string \""<< qPrintable(pluginImportVersion) << "\"." - << std::endl; - return EXIT_INVALIDARGUMENTS; - } - if (major != qtQmlMajorVersion) { - std::cerr << "Unsupported version \"" << qPrintable(pluginImportVersion) - << "\": Major version number must be \"" << qtQmlMajorVersion << "\"." - << std::endl; - return EXIT_INVALIDARGUMENTS; - } - metas = defaultReachable; - for (const QMetaObject *m : qAsConst(defaultReachable)) { - if (m->className() == QLatin1String("Qt")) { - metas.remove(m); - break; - } - } + QMap<QString, QList<QQmlType>> defaultCompositeTypes; + QSet<const QMetaObject *> builtins = collectReachableMetaObjects(&engine, uncreatableMetas, singletonMetas, defaultCompositeTypes, {QLatin1String("Qt"), majorVersion, minorVersion, strict}); + Q_ASSERT(builtins.size() == 1); + metas.insert(*builtins.begin()); } else { + auto versionSplitted = pluginImportVersion.split("."); + bool ok = versionSplitted.size() == 2; + if (!ok) + qCritical("Invalid version number"); + else { + majorVersion = versionSplitted.at(0).toInt(&ok); + if (!ok) + qCritical("Invalid major version"); + minorVersion = versionSplitted.at(1).toInt(&ok); + if (!ok) + qCritical("Invalid minor version"); + } + QList<QQmlType> defaultTypes = QQmlMetaType::qmlTypes(); // find a valid QtQuick import QByteArray importCode; QQmlType qtObjectType = QQmlMetaType::qmlType(&QObject::staticMetaObject); @@ -1273,25 +1316,16 @@ int main(int argc, char *argv[]) return EXIT_IMPORTERROR; } } + info = {pluginImportUri, majorVersion, minorVersion, strict}; + QSet<const QMetaObject *> candidates = collectReachableMetaObjects(&engine, uncreatableMetas, singletonMetas, compositeTypes, info, defaultTypes); - QSet<const QMetaObject *> candidates = collectReachableMetaObjects(&engine, uncreatableMetas, singletonMetas, compositeTypes, defaultTypes); - candidates.subtract(defaultReachable); - - for (QString iter: compositeTypes.keys()) { - if (defaultCompositeTypes.contains(iter)) { - QSet<QQmlType> compositeTypesByName = compositeTypes.value(iter); - compositeTypesByName.subtract(defaultCompositeTypes.value(iter)); - compositeTypes[iter] = compositeTypesByName; - } + for (auto it = compositeTypes.begin(), end = compositeTypes.end(); it != end; ++it) { + std::sort(it->begin(), it->end()); + it->erase(std::unique(it->begin(), it->end()), it->end()); } - // Also eliminate meta objects with the same classname. - // This is required because extended objects seem not to share - // a single meta object instance. - for (const QMetaObject *mo : qAsConst(defaultReachable)) - defaultReachableNames.insert(QByteArray(mo->className())); for (const QMetaObject *mo : qAsConst(candidates)) { - if (!defaultReachableNames.contains(mo->className())) + if (mo->className() != QLatin1String("Qt")) metas.insert(mo); } } @@ -1338,9 +1372,9 @@ int main(int argc, char *argv[]) dumper.dump(QQmlEnginePrivate::get(&engine), meta, uncreatableMetas.contains(meta), singletonMetas.contains(meta)); } - QMap<QString, QSet<QQmlType> >::const_iterator iter = compositeTypes.constBegin(); + QMap<QString, QList<QQmlType>>::const_iterator iter = compositeTypes.constBegin(); for (; iter != compositeTypes.constEnd(); ++iter) - dumper.dumpComposite(&engine, iter.value(), defaultReachableNames); + dumper.dumpComposite(&engine, iter.value(), info); // define QEasingCurve as an extension of QQmlEasingValueType, this way // properties using the QEasingCurve type get useful type information. diff --git a/tools/qmlprofiler/qmlprofilerapplication.cpp b/tools/qmlprofiler/qmlprofilerapplication.cpp index f92ffa9ff5..7b010546c3 100644 --- a/tools/qmlprofiler/qmlprofilerapplication.cpp +++ b/tools/qmlprofiler/qmlprofilerapplication.cpp @@ -302,7 +302,7 @@ void QmlProfilerApplication::flush() { if (m_recording) { m_pendingRequest = REQUEST_FLUSH; - m_qmlProfilerClient->sendRecordingStatus(false); + m_qmlProfilerClient->setRecording(false); } else { if (m_profilerData->save(m_interactiveOutputFile)) { m_profilerData->clear(); @@ -392,7 +392,7 @@ void QmlProfilerApplication::userCommand(const QString &command) if (cmd == Constants::CMD_RECORD || cmd == Constants::CMD_RECORD2) { m_pendingRequest = REQUEST_TOGGLE_RECORDING; - m_qmlProfilerClient->sendRecordingStatus(!m_recording); + m_qmlProfilerClient->setRecording(!m_recording); } else if (cmd == Constants::CMD_QUIT || cmd == Constants::CMD_QUIT2) { m_pendingRequest = REQUEST_QUIT; if (m_recording) { diff --git a/tools/qmlprofiler/qmlprofilerdata.cpp b/tools/qmlprofiler/qmlprofilerdata.cpp index d5662a0182..9ec143975e 100644 --- a/tools/qmlprofiler/qmlprofilerdata.cpp +++ b/tools/qmlprofiler/qmlprofilerdata.cpp @@ -101,7 +101,6 @@ QmlProfilerData::~QmlProfilerData() void QmlProfilerData::clear() { - d->eventTypes.clear(); d->events.clear(); d->traceEndTime = std::numeric_limits<qint64>::min(); diff --git a/tools/qmlscene/main.cpp b/tools/qmlscene/main.cpp index af50acaa5a..260c5bb7d1 100644 --- a/tools/qmlscene/main.cpp +++ b/tools/qmlscene/main.cpp @@ -29,7 +29,7 @@ #include <QtCore/qabstractanimation.h> #include <QtCore/qdir.h> #include <QtCore/qmath.h> -#include <QtCore/qdatetime.h> +#include <QtCore/qelapsedtimer.h> #include <QtCore/qpointer.h> #include <QtCore/qscopedpointer.h> #include <QtCore/qtextstream.h> @@ -76,7 +76,7 @@ QVector<int> RenderStatistics::timesPerFrames; void RenderStatistics::updateStats() { - static QTime time; + static QElapsedTimer time; static int frames; static int lastTime; @@ -169,10 +169,13 @@ struct Options bool multisample = false; bool coreProfile = false; bool verbose = false; + bool rhi = false; + bool rhiBackendSet = false; QVector<Qt::ApplicationAttribute> applicationAttributes; QString translationFile; QmlApplicationType applicationType = DefaultQmlApplicationType; QQuickWindow::TextRenderType textRenderType; + QString rhiBackend; }; #if defined(QMLSCENE_BUNDLE) @@ -271,6 +274,10 @@ static bool checkVersion(const QUrl &url) QTextStream stream(&f); bool codeFound= false; while (!codeFound) { + if (stream.atEnd()) { + fprintf(stderr, "qmlscene: no code found in file '%s'.\n", qPrintable(fileName)); + return false; + } QString line = stream.readLine(); if (line.contains(QLatin1Char('{'))) { codeFound = true; @@ -345,24 +352,26 @@ static void usage() puts(" --transparent .................... Make the window transparent"); puts(" --multisample .................... Enable multisampling (OpenGL anti-aliasing)"); puts(" --core-profile ................... Request a core profile OpenGL context"); + puts(" --rhi [vulkan|metal|d3d11|gl] .... Use the Qt graphics abstraction (RHI) instead of OpenGL directly.\n" + " .... Backend has platform specific defaults. Specify to override."); puts(" --no-version-detection ........... Do not try to detect the version of the .qml file"); puts(" --slow-animations ................ Run all animations in slow motion"); puts(" --resize-to-root ................. Resize the window to the size of the root item"); puts(" --quit ........................... Quit immediately after starting"); puts(" --disable-context-sharing ........ Disable the use of a shared GL context for QtQuick Windows\n" - " .........(remove AA_ShareOpenGLContexts)"); - puts(" --desktop..........................Force use of desktop GL (AA_UseDesktopOpenGL)"); - puts(" --gles.............................Force use of GLES (AA_UseOpenGLES)"); - puts(" --software.........................Force use of software rendering (AA_UseOpenGLES)"); - puts(" --scaling..........................Enable High DPI scaling (AA_EnableHighDpiScaling)"); - puts(" --no-scaling.......................Disable High DPI scaling (AA_DisableHighDpiScaling)"); - puts(" --verbose..........................Print version and graphical diagnostics for the run-time"); + " ........ (remove AA_ShareOpenGLContexts)"); + puts(" --desktop......................... Force use of desktop GL (AA_UseDesktopOpenGL)"); + puts(" --gles............................ Force use of GLES (AA_UseOpenGLES)"); + puts(" --software........................ Force use of software rendering (AA_UseOpenGLES)"); + puts(" --scaling......................... Enable High DPI scaling (AA_EnableHighDpiScaling)"); + puts(" --no-scaling...................... Disable High DPI scaling (AA_DisableHighDpiScaling)"); + puts(" --verbose......................... Print version and graphical diagnostics for the run-time"); #ifdef QT_WIDGETS_LIB - puts(" --apptype [gui|widgets] ...........Select which application class to use. Default is widgets."); + puts(" --apptype [gui|widgets] .......... Select which application class to use. Default is widgets."); #endif - puts(" --textrendertype [qt|native].......Select the default render type for text-like elements."); + puts(" --textrendertype [qt|native]...... Select the default render type for text-like elements."); puts(" -I <path> ........................ Add <path> to the list of import paths"); - puts(" -S <selector> .....................Add <selector> to the list of QQmlFileSelector selectors"); + puts(" -S <selector> .................... Add <selector> to the list of QQmlFileSelector selectors"); puts(" -P <path> ........................ Add <path> to the list of plugin paths"); puts(" -translation <translationfile> ... Set the language to run in"); @@ -546,7 +555,13 @@ int main(int argc, char ** argv) options.resizeViewToRootItem = true; else if (lowerArgument == QLatin1String("--verbose")) options.verbose = true; - else if (lowerArgument == QLatin1String("-i") && i + 1 < size) + else if (lowerArgument == QLatin1String("--rhi")) { + options.rhi = true; + if (i + 1 < size && !arguments.at(i + 1).startsWith(QLatin1Char('-'))) { + options.rhiBackendSet = true; + options.rhiBackend = arguments.at(++i); + } + } else if (lowerArgument == QLatin1String("-i") && i + 1 < size) imports.append(arguments.at(++i)); else if (lowerArgument == QLatin1String("-s") && i + 1 < size) customSelectors.append(arguments.at(++i)); @@ -588,6 +603,14 @@ int main(int argc, char ** argv) QUnifiedTimer::instance()->setSlowModeEnabled(options.slowAnimations); + if (options.rhi) { + qputenv("QSG_RHI", "1"); + if (options.rhiBackendSet) + qputenv("QSG_RHI_BACKEND", options.rhiBackend.toLatin1()); + else + qunsetenv("QSG_RHI_BACKEND"); + } + if (options.url.isEmpty()) #if defined(QMLSCENE_BUNDLE) displayOptionsDialog(&options); @@ -692,6 +715,8 @@ int main(int argc, char ** argv) // QQuickView if one was created. That case is tracked by // QPointer, so it is safe to delete the component here. delete component; + } else { + exitCode = 1; } } diff --git a/tools/qmltime/qmltime.cpp b/tools/qmltime/qmltime.cpp index b897d304fc..7baedff611 100644 --- a/tools/qmltime/qmltime.cpp +++ b/tools/qmltime/qmltime.cpp @@ -29,7 +29,7 @@ #include <QQmlComponent> #include <QDebug> #include <QGuiApplication> -#include <QTime> +#include <QElapsedTimer> #include <QQmlContext> #include <QQuickView> #include <QQuickItem> @@ -119,7 +119,7 @@ void Timer::setWillParent(bool p) void Timer::runTest(QQmlContext *context, uint iterations) { - QTime t; + QElapsedTimer t; t.start(); for (uint ii = 0; ii < iterations; ++ii) { QObject *o = m_component->create(context); diff --git a/tools/qmlcachegen/resourcefilemapper.cpp b/tools/shared/resourcefilemapper.cpp index 6a00b39f2e..b9cf463575 100644 --- a/tools/qmlcachegen/resourcefilemapper.cpp +++ b/tools/shared/resourcefilemapper.cpp @@ -50,17 +50,15 @@ bool ResourceFileMapper::isEmpty() const QStringList ResourceFileMapper::resourcePaths(const QString &fileName) { const QString absPath = QDir::cleanPath(QDir::current().absoluteFilePath(fileName)); - QHashIterator<QString, QString> it(qrcPathToFileSystemPath); QStringList resourcePaths; - while (it.hasNext()) { - it.next(); + for (auto it = qrcPathToFileSystemPath.cbegin(), end = qrcPathToFileSystemPath.cend(); it != end; ++it) { if (QFileInfo(it.value()) == QFileInfo(absPath)) resourcePaths.append(it.key()); } return resourcePaths; } -QStringList ResourceFileMapper::qmlCompilerFiles() const +QStringList ResourceFileMapper::qmlCompilerFiles(FileOutput fo) const { QStringList files; for (auto it = qrcPathToFileSystemPath.constBegin(), end = qrcPathToFileSystemPath.constEnd(); @@ -69,7 +67,10 @@ QStringList ResourceFileMapper::qmlCompilerFiles() const const QString suffix = QFileInfo(qrcPath).suffix(); if (suffix != QStringLiteral("qml") && suffix != QStringLiteral("js") && suffix != QStringLiteral("mjs")) continue; - files << qrcPath; + if (fo == FileOutput::AbsoluteFilePath) + files << it.value(); + else + files << qrcPath; } return files; } diff --git a/tools/qmlcachegen/resourcefilemapper.h b/tools/shared/resourcefilemapper.h index 2e0ab45171..ed3e486149 100644 --- a/tools/qmlcachegen/resourcefilemapper.h +++ b/tools/shared/resourcefilemapper.h @@ -34,12 +34,16 @@ struct ResourceFileMapper { + enum class FileOutput { + RelativeFilePath, + AbsoluteFilePath + }; ResourceFileMapper(const QStringList &resourceFiles); bool isEmpty() const; QStringList resourcePaths(const QString &fileName); - QStringList qmlCompilerFiles() const; + QStringList qmlCompilerFiles(FileOutput fo = FileOutput::RelativeFilePath) const; private: void populateFromQrcFile(QFile &file); diff --git a/tools/shared/shared.pri b/tools/shared/shared.pri new file mode 100644 index 0000000000..c094b51d5f --- /dev/null +++ b/tools/shared/shared.pri @@ -0,0 +1,3 @@ +INCLUDEPATH += $$PWD +SOURCES += $$PWD/resourcefilemapper.cpp +HEADERS += $$PWD/resourcefilemapper.h diff --git a/tools/tools.pro b/tools/tools.pro index 73cb6e2293..25ed760903 100644 --- a/tools/tools.pro +++ b/tools/tools.pro @@ -10,7 +10,7 @@ qtConfig(qml-devtools) { qtConfig(commandlineparser):qtConfig(xmlstreamwriter): SUBDIRS += qmlcachegen } -qtConfig(thread):!android|android_app:!wasm { +qtConfig(thread):!android|android_app:!wasm:!rtems { SUBDIRS += \ qml |