diff options
Diffstat (limited to 'tools')
142 files changed, 9853 insertions, 7504 deletions
diff --git a/tools/.prev_CMakeLists.txt b/tools/.prev_CMakeLists.txt deleted file mode 100644 index 324da4ee59..0000000000 --- a/tools/.prev_CMakeLists.txt +++ /dev/null @@ -1,42 +0,0 @@ -# Generated from tools.pro. - - -qt_exclude_tool_directories_from_default_target( - qmlprofiler - qmlplugindump - qmleasing -) - -if(QT_FEATURE_qml_devtools) - add_subdirectory(qmllint) - add_subdirectory(qmlimportscanner) - add_subdirectory(qmlformat) -endif() -if(QT_FEATURE_qml_devtools AND QT_FEATURE_xmlstreamwriter) - add_subdirectory(qmlcachegen) -endif() -if(QT_FEATURE_thread AND NOT ANDROID AND NOT WASM AND NOT rtems) - add_subdirectory(qml) -endif() -if(QT_FEATURE_qml_profiler AND QT_FEATURE_thread AND NOT ANDROID AND NOT WASM AND NOT rtems) - add_subdirectory(qmlprofiler) -endif() -if(QT_FEATURE_qml_preview AND QT_FEATURE_thread AND NOT ANDROID AND NOT WASM AND NOT rtems) - add_subdirectory(qmlpreview) -endif() -if(QT_BUILD_SHARED_LIBS AND QT_FEATURE_thread AND TARGET Qt::Quick AND NOT ANDROID AND NOT WASM AND NOT rtems) - add_subdirectory(qmlscene) - add_subdirectory(qmltime) -endif() -if(QT_BUILD_SHARED_LIBS AND QT_FEATURE_process AND QT_FEATURE_regularexpression AND QT_FEATURE_thread AND TARGET Qt::Quick AND NOT ANDROID AND NOT WASM AND NOT rtems) - add_subdirectory(qmlplugindump) -endif() -if(QT_FEATURE_dialogbuttonbox AND QT_FEATURE_thread AND TARGET Qt::Quick AND TARGET Qt::Widgets AND NOT ANDROID AND NOT WASM AND NOT rtems) - add_subdirectory(qmleasing) -endif() -if(QT_FEATURE_thread AND TARGET Qt::QuickTest AND NOT ANDROID AND NOT WASM AND NOT rtems) - add_subdirectory(qmltestrunner) -endif() -if(QT_FEATURE_private_tests AND QT_FEATURE_thread AND NOT ANDROID AND NOT WASM AND NOT rtems) - add_subdirectory(qmljs) -endif() diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index 8438e8c6fa..0b89bea46e 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -1,46 +1,72 @@ -# Generated from tools.pro. - +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause -qt_exclude_tool_directories_from_default_target( - qmlprofiler - qmlplugindump - qmleasing -) +# Generated from tools.pro. -if(QT_FEATURE_qml_devtools) +add_subdirectory(qmldom) +if(QT_FEATURE_commandlineparser) add_subdirectory(qmllint) - add_subdirectory(qmlimportscanner) - add_subdirectory(qmlformat) + add_subdirectory(qmltc) + add_subdirectory(qmltyperegistrar) + add_subdirectory(qmljsrootgen) +endif() +add_subdirectory(qmlimportscanner) +add_subdirectory(qmlformat) +if(TARGET Qt::LanguageServerPrivate AND QT_FEATURE_commandlineparser AND QT_FEATURE_filesystemwatcher) + if (NOT CMAKE_CROSSCOMPILING OR QT_FORCE_BUILD_TOOLS) + add_subdirectory(qmlls) + endif() endif() -if(QT_FEATURE_qml_devtools AND QT_FEATURE_xmlstreamwriter) +if(QT_FEATURE_xmlstreamwriter) # special case begin # Do not build qmlcachegen here but build it at src/ # time, so that we can use it for our own .qml files in src/imports. # add_subdirectory(qmlcachegen) # special case end endif() -if(QT_FEATURE_thread AND NOT ANDROID AND NOT WASM AND NOT rtems) - add_subdirectory(qml) -endif() -if(QT_FEATURE_qml_profiler AND QT_FEATURE_thread AND NOT ANDROID AND NOT WASM AND NOT rtems) - add_subdirectory(qmlprofiler) -endif() -if(QT_FEATURE_qml_preview AND QT_FEATURE_thread AND NOT ANDROID AND NOT WASM AND NOT rtems) - add_subdirectory(qmlpreview) -endif() -if(QT_BUILD_SHARED_LIBS AND QT_FEATURE_thread AND TARGET Qt::Quick AND NOT ANDROID AND NOT WASM AND NOT rtems) - add_subdirectory(qmlscene) - add_subdirectory(qmltime) -endif() -if(QT_BUILD_SHARED_LIBS AND QT_FEATURE_process AND QT_FEATURE_regularexpression AND QT_FEATURE_thread AND TARGET Qt::Quick AND NOT ANDROID AND NOT WASM AND NOT rtems) - add_subdirectory(qmlplugindump) -endif() -if(QT_FEATURE_dialogbuttonbox AND QT_FEATURE_thread AND TARGET Qt::Quick AND TARGET Qt::Widgets AND NOT ANDROID AND NOT WASM AND NOT rtems) - add_subdirectory(qmleasing) -endif() -if(QT_FEATURE_thread AND TARGET Qt::QuickTest AND NOT ANDROID AND NOT WASM AND NOT rtems) - add_subdirectory(qmltestrunner) -endif() -if(QT_FEATURE_private_tests AND QT_FEATURE_thread AND NOT ANDROID AND NOT WASM AND NOT rtems) - add_subdirectory(qmljs) -endif() + +if(NOT (ANDROID OR WASM OR IOS OR VISIONOS OR rtems)) + if(QT_FEATURE_thread) + add_subdirectory(qml) + if(QT_FEATURE_qml_profiler) + add_subdirectory(qmlprofiler) + endif() + if(QT_FEATURE_qml_preview) + add_subdirectory(qmlpreview) + endif() + if(QT_BUILD_SHARED_LIBS AND TARGET Qt::Quick) + add_subdirectory(qmlscene) + add_subdirectory(qmltime) + endif() + if(QT_BUILD_SHARED_LIBS + AND QT_FEATURE_process + AND QT_FEATURE_regularexpression + AND TARGET Qt::Quick) + add_subdirectory(qmlplugindump) + endif() + if(TARGET Qt::QuickTest) + add_subdirectory(qmltestrunner) + endif() + if(QT_FEATURE_private_tests) + add_subdirectory(qmljs) + endif() + endif() # QT_FEATURE_thread + + if(TARGET Qt::Quick + AND TARGET Qt::Widgets + AND QT_FEATURE_checkbox + AND QT_FEATURE_combobox + AND QT_FEATURE_dialogbuttonbox + AND QT_FEATURE_formlayout + AND QT_FEATURE_groupbox + AND QT_FEATURE_lineedit + AND QT_FEATURE_mainwindow + AND QT_FEATURE_spinbox + AND QT_FEATURE_textedit) + add_subdirectory(qmleasing) + endif() + + if(TARGET Qt::Quick AND TARGET Qt::Svg) + add_subdirectory(svgtoqml) + endif() +endif() # NOT (ANDROID OR WASM OR IOS OR rtems) diff --git a/tools/qml/.prev_CMakeLists.txt b/tools/qml/.prev_CMakeLists.txt deleted file mode 100644 index 7a278d970e..0000000000 --- a/tools/qml/.prev_CMakeLists.txt +++ /dev/null @@ -1,72 +0,0 @@ -# Generated from qml.pro. - -##################################################################### -## qml Tool: -##################################################################### - -qt_get_tool_target_name(target_name qml) -qt_internal_add_tool(${target_name} - TARGET_DESCRIPTION "QML Runtime" - SOURCES - conf.h - main.cpp - PUBLIC_LIBRARIES - Qt::CorePrivate - Qt::QmlPrivate -) - -# Resources: -set(qml_resource_files - "conf/content/resizeItemToWindow.qml" - "conf/content/resizeWindowToItem.qml" - "conf/default.qml" - "conf/resizeToItem.qml" - "resources/qml-64.png" -) - -qt_internal_add_resource(${target_name} "qml" - PREFIX - "/qt-project.org/QmlRuntime" - FILES - ${qml_resource_files} -) - - -#### Keys ignored in scope 1:.:.:qml.pro:<TRUE>: -# ICON = "resources/qml-64.png" -# QMAKE_TARGET_DESCRIPTION = "QML" "Runtime" -# QML_IMPORT_NAME = "QmlRuntime.Config" -# QML_IMPORT_VERSION = "1.0" - -## Scopes: -##################################################################### - -qt_internal_extend_target(${target_name} CONDITION TARGET Qt::Gui - PUBLIC_LIBRARIES - Qt::Gui -) - -qt_internal_extend_target(${target_name} CONDITION TARGET Qt::Widgets - PUBLIC_LIBRARIES - Qt::Widgets -) - -#### Keys ignored in scope 4:.:.:qml.pro:WIN32: -# RC_ICONS = "resources/qml.ico" - -#### Keys ignored in scope 5:.:.:qml.pro:APPLE: -# ICON = "resources/qml.icns" -# OTHER_FILES = "resources/Info.plist" -# QMAKE_INFO_PLIST = "resources/Info.plist" - -qt_internal_extend_target(${target_name} CONDITION QT_FEATURE_qml_debug - DEFINES - QT_QML_DEBUG_NO_WARNING -) - -set_target_properties(${target_name} PROPERTIES - QT_QML_MODULE_VERSION 1.0 - QT_QML_MODULE_URI QmlRuntime.Config -) - -qt6_qml_type_registration(${target_name}) diff --git a/tools/qml/CMakeLists.txt b/tools/qml/CMakeLists.txt index 96d030074d..b209c730d3 100644 --- a/tools/qml/CMakeLists.txt +++ b/tools/qml/CMakeLists.txt @@ -1,89 +1,49 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + # Generated from qml.pro. ##################################################################### -## qml Tool: +## qml App: ##################################################################### -qt_get_tool_target_name(target_name qml) -qt_internal_add_tool(${target_name} +qt_internal_add_app(qml TARGET_DESCRIPTION "QML Runtime" - TOOLS_TARGET Qml # special case SOURCES conf.h main.cpp - PUBLIC_LIBRARIES + LIBRARIES Qt::CorePrivate Qt::QmlPrivate ) -# special case begin -# Don't set properties on a host tool when cross compiling, because it -# is not being built. -if(CMAKE_CROSSCOMPILING AND NOT QT_BUILD_TOOLS_WHEN_CROSSCOMPILING) - return() -endif() - -set_source_files_properties( - conf/default.qml - conf/resizeToItem.qml - conf/content/resizeItemToWindow.qml - conf/content/resizeWindowToItem.qml - PROPERTIES QT_SKIP_QUICKCOMPILER 1 -) -# special case end - -# Resources: -set(qml_resource_files - "conf/content/resizeItemToWindow.qml" - "conf/content/resizeWindowToItem.qml" - "conf/default.qml" - "conf/resizeToItem.qml" - "resources/qml-64.png" -) +set_target_properties(qml PROPERTIES WIN32_EXECUTABLE FALSE) -qt_internal_add_resource(${target_name} "qml" - PREFIX - "/qt-project.org/QmlRuntime" - FILES - ${qml_resource_files} +# Turn the tool into its own self-contained qml module +qt6_add_qml_module(qml + RESOURCE_PREFIX "/qt-project.org/imports" + URI QmlRuntime.Config + VERSION 1.0 + QML_FILES + default.qml + resizeToItem.qml + ResizeItemToWindow.qml + ResizeWindowToItem.qml + RESOURCES + resources/qml-64.png ) - -#### Keys ignored in scope 1:.:.:qml.pro:<TRUE>: -# ICON = "resources/qml-64.png" -# QMAKE_TARGET_DESCRIPTION = "QML" "Runtime" -# QML_IMPORT_NAME = "QmlRuntime.Config" -# QML_IMPORT_VERSION = "1.0" - -## Scopes: -##################################################################### - -qt_internal_extend_target(${target_name} CONDITION TARGET Qt::Gui +qt_internal_extend_target(qml CONDITION TARGET Qt::Gui PUBLIC_LIBRARIES Qt::Gui ) -qt_internal_extend_target(${target_name} CONDITION TARGET Qt::Widgets +qt_internal_extend_target(qml CONDITION TARGET Qt::Widgets PUBLIC_LIBRARIES Qt::Widgets ) -#### Keys ignored in scope 4:.:.:qml.pro:WIN32: -# RC_ICONS = "resources/qml.ico" - -#### Keys ignored in scope 5:.:.:qml.pro:APPLE: -# ICON = "resources/qml.icns" -# OTHER_FILES = "resources/Info.plist" -# QMAKE_INFO_PLIST = "resources/Info.plist" - -qt_internal_extend_target(${target_name} CONDITION QT_FEATURE_qml_debug +qt_internal_extend_target(qml CONDITION QT_FEATURE_qml_debug DEFINES QT_QML_DEBUG_NO_WARNING ) - -set_target_properties(${target_name} PROPERTIES - QT_QML_MODULE_VERSION 1.0 - QT_QML_MODULE_URI QmlRuntime.Config -) - -qt6_qml_type_registration(${target_name}) diff --git a/tools/qml/ResizeItemToWindow.qml b/tools/qml/ResizeItemToWindow.qml new file mode 100644 index 0000000000..a4d8bfec40 --- /dev/null +++ b/tools/qml/ResizeItemToWindow.qml @@ -0,0 +1,20 @@ +// Copyright (C) 2019 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +import QtQuick.Window 2.0 +import QtQuick 2.0 + +Window { + property Item containedObject: null + onContainedObjectChanged: { + if (containedObject == undefined || containedObject == null) { + visible = false; + return; + } + width = containedObject.width; + height = containedObject.height; + containedObject.parent = contentItem; + visible = true; + } + onWidthChanged: if (containedObject) containedObject.width = width + onHeightChanged: if (containedObject) containedObject.height = height +} diff --git a/tools/qml/ResizeWindowToItem.qml b/tools/qml/ResizeWindowToItem.qml new file mode 100644 index 0000000000..b969971bc2 --- /dev/null +++ b/tools/qml/ResizeWindowToItem.qml @@ -0,0 +1,18 @@ +// Copyright (C) 2019 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +import QtQuick.Window 2.0 +import QtQuick 2.0 + +Window { + property Item containedObject: null + onContainedObjectChanged: { + if (containedObject == undefined || containedObject == null) { + visible = false; + return; + } + 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.h b/tools/qml/conf.h index 84167c9134..d105359037 100644 --- a/tools/qml/conf.h +++ b/tools/qml/conf.h @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 Research In Motion. -** 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$ -** -****************************************************************************/ +// Copyright (C) 2016 Research In Motion. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #ifndef CONF_H #define CONF_H @@ -42,7 +17,7 @@ class PartialScene : public QObject QML_ELEMENT QML_ADDED_IN_VERSION(1, 0) public: - PartialScene(QObject *parent = 0) : QObject(parent) + PartialScene(QObject *parent = nullptr) : QObject(parent) {} const QUrl container() const { return m_container; } @@ -52,16 +27,16 @@ public: if (a==m_container) return; m_container = a; - emit containerChanged(); + Q_EMIT containerChanged(); } void setItemType(const QString &a) { if (a==m_itemType) return; m_itemType = a; - emit itemTypeChanged(); + Q_EMIT itemTypeChanged(); } -signals: +Q_SIGNALS: void containerChanged(); void itemTypeChanged(); @@ -78,8 +53,7 @@ class Config : public QObject QML_NAMED_ELEMENT(Configuration) QML_ADDED_IN_VERSION(1, 0) public: - Config (QObject* parent=0) : QObject(parent) - {} + Config (QObject *parent = nullptr) : QObject(parent) {} QQmlListProperty<PartialScene> sceneCompleters() { diff --git a/tools/qml/conf/content/resizeItemToWindow.qml b/tools/qml/conf/content/resizeItemToWindow.qml deleted file mode 100644 index a645cf8ea9..0000000000 --- a/tools/qml/conf/content/resizeItemToWindow.qml +++ /dev/null @@ -1,70 +0,0 @@ -/***************************************************************************** -** -** 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/content/resizeWindowToItem.qml b/tools/qml/conf/content/resizeWindowToItem.qml deleted file mode 100644 index cd03e5065a..0000000000 --- a/tools/qml/conf/content/resizeWindowToItem.qml +++ /dev/null @@ -1,65 +0,0 @@ -/***************************************************************************** -** -** 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 - onContainedObjectChanged: { - if (containedObject == undefined || containedObject == null) { - visible = false; - return; - } - 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/default.qml b/tools/qml/conf/default.qml deleted file mode 100644 index 5c107b2876..0000000000 --- a/tools/qml/conf/default.qml +++ /dev/null @@ -1,57 +0,0 @@ -/***************************************************************************** -** -** 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: "qrc:/qt-project.org/QmlRuntime/conf/content/resizeItemToWindow.qml" - } -} diff --git a/tools/qml/conf/resizeToItem.qml b/tools/qml/conf/resizeToItem.qml deleted file mode 100644 index 480995a6b0..0000000000 --- a/tools/qml/conf/resizeToItem.qml +++ /dev/null @@ -1,57 +0,0 @@ -/***************************************************************************** -** -** 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/default.qml b/tools/qml/default.qml new file mode 100644 index 0000000000..54a521193c --- /dev/null +++ b/tools/qml/default.qml @@ -0,0 +1,10 @@ +// Copyright (C) 2019 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +import QmlRuntime.Config + +Configuration { + PartialScene { + itemType: "QQuickItem" + container: "qrc:/qt-project.org/imports/QmlRuntime/Config/ResizeItemToWindow.qml" + } +} diff --git a/tools/qml/main.cpp b/tools/qml/main.cpp index 8e5a493bcd..da544c5563 100644 --- a/tools/qml/main.cpp +++ b/tools/qml/main.cpp @@ -1,31 +1,6 @@ -/**************************************************************************** -** -** Copyright (C) 2016 Research In Motion. -** 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$ -** -****************************************************************************/ +// Copyright (C) 2016 Research In Motion. +// Copyright (C) 2019 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "conf.h" @@ -61,6 +36,7 @@ #include <qqmlfileselector.h> #include <private/qtqmlglobal_p.h> +#include <private/qqmlimport_p.h> #if QT_CONFIG(qml_animation) #include <private/qabstractanimation_p.h> #endif @@ -72,6 +48,8 @@ #define FILE_OPEN_EVENT_WAIT_TIME 3000 // ms +Q_LOGGING_CATEGORY(lcDeprecated, "qt.tools.qml.deprecated") + enum QmlApplicationType { QmlApplicationTypeUnknown , QmlApplicationTypeCore @@ -95,10 +73,18 @@ static QQmlApplicationEngine *qae = nullptr; #if defined(Q_OS_DARWIN) || defined(QT_GUI_LIB) static int exitTimerId = -1; #endif -static const QString iconResourcePath(QStringLiteral(":/qt-project.org/QmlRuntime/resources/qml-64.png")); -static const QString confResourcePath(QStringLiteral(":/qt-project.org/QmlRuntime/conf/")); +static const QString iconResourcePath(QStringLiteral(":/qt-project.org/imports/QmlRuntime/Config/resources/qml-64.png")); +static const QString confResourcePath(QStringLiteral(":/qt-project.org/imports/QmlRuntime/Config/")); +static const QString customConfFileName(QStringLiteral("configuration.qml")); static bool verboseMode = false; static bool quietMode = false; +static bool glShareContexts = true; +static bool disableShaderCache = true; +#if defined(QT_GUI_LIB) +static bool requestAlphaChannel = false; +static bool requestMSAA = false; +static bool requestCoreProfile = false; +#endif static void loadConf(const QString &override, bool quiet) // Terminates app on failure { @@ -109,27 +95,31 @@ static void loadConf(const QString &override, bool quiet) // Terminates app on f QFileInfo fi; fi.setFile(QStandardPaths::locate(QStandardPaths::AppDataLocation, defaultFileName)); if (fi.exists()) { - settingsUrl = QUrl::fromLocalFile(fi.absoluteFilePath()); + settingsUrl = QQmlImports::urlFromLocalFileOrQrcOrUrl(fi.absoluteFilePath()); } else { // ### If different built-in configs are needed per-platform, just apply QFileSelector to the qrc conf.qml path fi.setFile(confResourcePath + defaultFileName); - settingsUrl = QUrl::fromLocalFile(fi.absoluteFilePath()); + settingsUrl = QQmlImports::urlFromLocalFileOrQrcOrUrl(fi.absoluteFilePath()); builtIn = true; } } else { QFileInfo fi; fi.setFile(confResourcePath + override + QLatin1String(".qml")); if (fi.exists()) { - settingsUrl = QUrl::fromLocalFile(fi.absoluteFilePath()); + settingsUrl = QQmlImports::urlFromLocalFileOrQrcOrUrl(fi.absoluteFilePath()); builtIn = true; } else { - fi.setFile(override); + fi.setFile(QDir(QStandardPaths::locate(QStandardPaths::AppConfigLocation, override, QStandardPaths::LocateDirectory)), customConfFileName); + if (fi.exists()) + settingsUrl = QQmlImports::urlFromLocalFileOrQrcOrUrl(fi.absoluteFilePath()); + 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 = QQmlImports::urlFromLocalFileOrQrcOrUrl(fi.absoluteFilePath()); } } @@ -166,10 +156,38 @@ void noFilesGiven() static void listConfFiles() { - QDir confResourceDir(confResourcePath); + const 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())); + for (const QFileInfo &fi : confResourceDir.entryInfoList(QDir::Files)) { + if (fi.completeSuffix() != QLatin1String("qml")) + continue; + + const QString baseName = fi.baseName(); + if (baseName.isEmpty() || baseName[0].isUpper()) + continue; + + printf(" %s\n", qPrintable(baseName)); + } + printf("%s\n", qPrintable(QCoreApplication::translate("main", "Other configurations:"))); + bool foundOther = false; + const QStringList otherLocations = QStandardPaths::standardLocations(QStandardPaths::AppConfigLocation); + for (const auto &confDirPath : otherLocations) { + const QDir confDir(confDirPath); + for (const QFileInfo &fi : confDir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot)) { + foundOther = true; + if (verboseMode) + printf(" %s\n", qPrintable(fi.absoluteFilePath())); + else + printf(" %s\n", qPrintable(fi.baseName())); + } + } + if (!foundOther) + printf(" %s\n", qPrintable(QCoreApplication::translate("main", "none"))); + if (verboseMode) { + printf("%s\n", qPrintable(QCoreApplication::translate("main", "Checked in:"))); + for (const auto &confDirPath : otherLocations) + printf(" %s\n", qPrintable(confDirPath)); + } exit(0); } @@ -230,18 +248,17 @@ public Q_SLOTS: { Q_UNUSED(url); if (o) { - checkForWindow(o); + ++createdObjects; if (conf && qae) - for (PartialScene *ps : qAsConst(conf->completers)) + for (PartialScene *ps : std::as_const(conf->completers)) if (o->inherits(ps->itemType().toUtf8().constData())) contain(o, ps->container()); } - if (haveWindow) - return; - if (! --expectedFileCount) { + if (!--expectedFileCount && !createdObjects) { printf("qml: Did not load any objects, exiting.\n"); - std::exit(2); // Different return code from qFatal + exit(2); + QCoreApplication::exit(2); } } @@ -257,11 +274,10 @@ public Q_SLOTS: private: void contain(QObject *o, const QUrl &containPath); - void checkForWindow(QObject *o); private: - bool haveWindow = false; int expectedFileCount; + int createdObjects = 0; }; void LoadWatcher::contain(QObject *o, const QUrl &containPath) @@ -270,7 +286,7 @@ void LoadWatcher::contain(QObject *o, const QUrl &containPath) QObject *o2 = c.create(); if (!o2) return; - checkForWindow(o2); + o2->setParent(this); bool success = false; int idx; if ((idx = o2->metaObject()->indexOfProperty("containedObject")) != -1) @@ -279,16 +295,6 @@ void LoadWatcher::contain(QObject *o, const QUrl &containPath) o->setParent(o2); // Set QObject parent, and assume container will react as needed } -void LoadWatcher::checkForWindow(QObject *o) -{ -#if defined(QT_GUI_LIB) - if (o->isWindowType() && o->inherits("QQuickWindow")) - haveWindow = true; -#else - Q_UNUSED(o); -#endif // QT_GUI_LIB -} - void quietMessageHandler(QtMsgType type, const QMessageLogContext &ctxt, const QString &msg) { Q_UNUSED(ctxt); @@ -330,6 +336,16 @@ static void getAppFlags(int argc, char **argv) QCoreApplication::setAttribute(Qt::AA_UseOpenGLES); } else if (!strcmp(argv[i], "-software") || !strcmp(argv[i], "--software")) { QCoreApplication::setAttribute(Qt::AA_UseSoftwareOpenGL); + } else if (!strcmp(argv[i], "-disable-context-sharing") || !strcmp(argv[i], "--disable-context-sharing")) { + glShareContexts = false; + } else if (!strcmp(argv[i], "-enable-shader-cache") || !strcmp(argv[i], "--enable-shader-cache")) { + disableShaderCache = false; + } else if (!strcmp(argv[i], "-transparent") || !strcmp(argv[i], "--transparent")) { + requestAlphaChannel = true; + } else if (!strcmp(argv[i], "-multisample") || !strcmp(argv[i], "--multisample")) { + requestMSAA = true; + } else if (!strcmp(argv[i], "-core-profile") || !strcmp(argv[i], "--core-profile")) { + requestCoreProfile = true; } } #else @@ -338,6 +354,7 @@ static void getAppFlags(int argc, char **argv) #endif // QT_GUI_LIB } +#if QT_DEPRECATED_SINCE(6, 3) static void loadDummyDataFiles(QQmlEngine &engine, const QString& directory) { QDir dir(directory+"/dummydata", "*.qml"); @@ -355,16 +372,44 @@ static void loadDummyDataFiles(QQmlEngine &engine, const QString& directory) if (dummyData && !quietMode) { printf("qml: Loaded dummy data: %s\n", qPrintable(dir.filePath(qml))); - qml.truncate(qml.length()-4); + qml.truncate(qml.size()-4); engine.rootContext()->setContextProperty(qml, dummyData); dummyData->setParent(&engine); } } } +#endif int main(int argc, char *argv[]) { getAppFlags(argc, argv); + + // Must set the default QSurfaceFormat before creating the app object if + // AA_ShareOpenGLContexts is going to be set. +#if defined(QT_GUI_LIB) + QSurfaceFormat surfaceFormat; + surfaceFormat.setDepthBufferSize(24); + surfaceFormat.setStencilBufferSize(8); + if (requestMSAA) + surfaceFormat.setSamples(4); + if (requestAlphaChannel) + surfaceFormat.setAlphaBufferSize(8); + if (qEnvironmentVariableIsSet("QSG_CORE_PROFILE") + || qEnvironmentVariableIsSet("QML_CORE_PROFILE") + || requestCoreProfile) + { + // intentionally requesting 4.1 core to play nice with macOS + surfaceFormat.setVersion(4, 1); + surfaceFormat.setProfile(QSurfaceFormat::CoreProfile); + } + QSurfaceFormat::setDefaultFormat(surfaceFormat); +#endif + + if (glShareContexts) + QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts); + if (disableShaderCache) + QCoreApplication::setAttribute(Qt::AA_DisableShaderDiskCache); + std::unique_ptr<QCoreApplication> app; switch (applicationType) { #ifdef QT_GUI_LIB @@ -390,18 +435,16 @@ int main(int argc, char *argv[]) app->setOrganizationDomain("qt-project.org"); QCoreApplication::setApplicationVersion(QLatin1String(QT_VERSION_STR)); - QQmlApplicationEngine e; QStringList files; QString confFile; QString translationFile; - QString dummyDir; // Handle main arguments QCommandLineParser parser; parser.setSingleDashWordOptionMode(QCommandLineParser::ParseAsLongOptions); parser.setOptionsAfterPositionalArgumentsMode(QCommandLineParser::ParseAsPositionalArguments); - const QCommandLineOption helpOption = parser.addHelpOption(); - const QCommandLineOption versionOption = parser.addVersionOption(); + parser.addHelpOption(); + 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."), @@ -427,9 +470,11 @@ int main(int argc, char *argv[]) QCommandLineOption translationOption(QStringLiteral("translation"), QCoreApplication::translate("main", "Load the given file as the translations file."), QStringLiteral("file")); parser.addOption(translationOption); +#if QT_DEPRECATED_SINCE(6, 3) QCommandLineOption dummyDataOption(QStringLiteral("dummy-data"), - QCoreApplication::translate("main", "Load QML files from the given directory as context properties."), QStringLiteral("file")); + QCoreApplication::translate("main", "Load QML files from the given directory as context properties. (deprecated)"), QStringLiteral("file")); parser.addOption(dummyDataOption); +#endif #ifdef QT_GUI_LIB // OpenGL options QCommandLineOption glDesktopOption(QStringLiteral("desktop"), @@ -441,7 +486,24 @@ int main(int argc, char *argv[]) QCommandLineOption glSoftwareOption(QStringLiteral("software"), QCoreApplication::translate("main", "Force use of software rendering (AA_UseSoftwareOpenGL).")); parser.addOption(glSoftwareOption); // Just for the help text... we've already handled this argument above + QCommandLineOption glCoreProfile(QStringLiteral("core-profile"), + QCoreApplication::translate("main", "Force use of OpenGL Core Profile.")); + parser.addOption(glCoreProfile); // Just for the help text... we've already handled this argument above + QCommandLineOption glContextSharing(QStringLiteral("disable-context-sharing"), + QCoreApplication::translate("main", "Disable the use of a shared GL context for QtQuick Windows")); + parser.addOption(glContextSharing); // Just for the help text... we've already handled this argument above + // Options relevant for other 3D APIs as well + QCommandLineOption shaderCaching(QStringLiteral("enable-shader-cache"), + QCoreApplication::translate("main", "Enable persistent caching of generated shaders")); + parser.addOption(shaderCaching); // Just for the help text... we've already handled this argument above + QCommandLineOption transparentOption(QStringLiteral("transparent"), + QCoreApplication::translate("main", "Requests an alpha channel in order to enable semi-transparent windows.")); + parser.addOption(transparentOption); // Just for the help text... we've already handled this argument above + QCommandLineOption multisampleOption(QStringLiteral("multisample"), + QCoreApplication::translate("main", "Requests 4x multisample antialiasing.")); + parser.addOption(multisampleOption); // Just for the help text... we've already handled this argument above #endif // QT_GUI_LIB + // Debugging and verbosity options QCommandLineOption quietOption(QStringLiteral("quiet"), QCoreApplication::translate("main", "Suppress all output.")); @@ -457,7 +519,7 @@ int main(int argc, char *argv[]) parser.addOption(fixedAnimationsOption); QCommandLineOption rhiOption(QStringList() << QStringLiteral("r") << QStringLiteral("rhi"), QCoreApplication::translate("main", "Set the backend for the Qt graphics abstraction (RHI). " - "Backend is one of: default, vulkan, metal, d3d11, gl"), + "Backend is one of: default, vulkan, metal, d3d11, d3d12, opengl"), QStringLiteral("backend")); parser.addOption(rhiOption); QCommandLineOption selectorOption(QStringLiteral("S"), QCoreApplication::translate("main", @@ -470,14 +532,13 @@ int main(int argc, char *argv[]) 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); + parser.process(*app); + if (parser.isSet(verboseOption)) + verboseMode = true; + if (parser.isSet(quietOption)) { + quietMode = true; + verboseMode = false; } - if (parser.isSet(versionOption)) - parser.showVersion(); - if (parser.isSet(helpOption)) - parser.showHelp(); if (parser.isSet(listConfOption)) listConfFiles(); if (applicationType == QmlApplicationTypeUnknown) { @@ -488,18 +549,15 @@ int main(int argc, char *argv[]) #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) if (parser.isSet(slowAnimationsOption)) QUnifiedTimer::instance()->setSlowModeEnabled(true); if (parser.isSet(fixedAnimationsOption)) QUnifiedTimer::instance()->setConsistentTiming(true); #endif + + QQmlApplicationEngine e; + for (const QString &importPath : parser.values(importOption)) e.addImportPath(importPath); @@ -510,24 +568,11 @@ int main(int argc, char *argv[]) if (!customSelectors.isEmpty()) e.setExtraFileSelectors(customSelectors); -#if defined(QT_GUI_LIB) - if (qEnvironmentVariableIsSet("QSG_CORE_PROFILE") || qEnvironmentVariableIsSet("QML_CORE_PROFILE")) { - QSurfaceFormat surfaceFormat; - surfaceFormat.setStencilBufferSize(8); - surfaceFormat.setDepthBufferSize(24); - surfaceFormat.setVersion(4, 1); - surfaceFormat.setProfile(QSurfaceFormat::CoreProfile); - QSurfaceFormat::setDefaultFormat(surfaceFormat); - } -#endif - 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)) { const QString rhiBackend = parser.value(rhiOption); if (rhiBackend == QLatin1String("default")) @@ -545,9 +590,8 @@ int main(int argc, char *argv[]) #if QT_CONFIG(translation) // Need to be installed before QQmlApplicationEngine's automatic translation loading // (qt_ translations are loaded there) + QTranslator translator; if (!translationFile.isEmpty()) { - QTranslator translator; - if (translator.load(translationFile)) { app->installTranslator(&translator); if (verboseMode) @@ -567,7 +611,7 @@ int main(int argc, char *argv[]) QLoggingCategory::setFilterRules(QStringLiteral("*=false")); } - if (files.count() <= 0) { + if (files.size() <= 0) { #if defined(Q_OS_DARWIN) && defined(QT_GUI_LIB) if (applicationType == QmlApplicationTypeGui) exitTimerId = static_cast<LoaderApplication *>(app.get())->startTimer(FILE_OPEN_EVENT_WAIT_TIME); @@ -580,13 +624,20 @@ int main(int argc, char *argv[]) loadConf(confFile, !verboseMode); // Load files - QScopedPointer<LoadWatcher> lw(new LoadWatcher(&e, files.count())); + QScopedPointer<LoadWatcher> lw(new LoadWatcher(&e, files.size())); +#if QT_DEPRECATED_SINCE(6, 3) + QString dummyDir; + if (parser.isSet(dummyDataOption)) + dummyDir = parser.value(dummyDataOption); // Load dummy data before loading QML-files - if (!dummyDir.isEmpty() && QFileInfo (dummyDir).isDir()) + if (!dummyDir.isEmpty() && QFileInfo (dummyDir).isDir()) { + qCWarning(lcDeprecated()) << "Warning: the qml --dummy-data option is deprecated and will be removed in a future version of Qt."; loadDummyDataFiles(e, dummyDir); + } +#endif - for (const QString &path : qAsConst(files)) { + for (const QString &path : std::as_const(files)) { QUrl url = QUrl::fromUserInput(path, QDir::currentPath(), QUrl::AssumeLocalFile); if (verboseMode) printf("qml: loading %s\n", qPrintable(url.toString())); diff --git a/tools/qml/qml.qrc b/tools/qml/qml.qrc deleted file mode 100644 index 69aa4a5756..0000000000 --- a/tools/qml/qml.qrc +++ /dev/null @@ -1,9 +0,0 @@ -<RCC> - <qresource prefix="qt-project.org/QmlRuntime"> - <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/qml/resizeToItem.qml b/tools/qml/resizeToItem.qml new file mode 100644 index 0000000000..5bddd8ebaa --- /dev/null +++ b/tools/qml/resizeToItem.qml @@ -0,0 +1,10 @@ +// Copyright (C) 2019 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +import QmlRuntime.Config + +Configuration { + PartialScene { + itemType: "QQuickItem" + container: "qrc:/qt-project.org/imports/QmlRuntime/Config/ResizeWindowToItem.qml" + } +} diff --git a/tools/qmlcachegen/.prev_CMakeLists.txt b/tools/qmlcachegen/.prev_CMakeLists.txt deleted file mode 100644 index a33e080bbe..0000000000 --- a/tools/qmlcachegen/.prev_CMakeLists.txt +++ /dev/null @@ -1,46 +0,0 @@ -# Generated from qmlcachegen.pro. - -##################################################################### -## qmlcachegen Tool: -##################################################################### - -qt_get_tool_target_name(target_name qmlcachegen) -qt_internal_add_tool(${target_name} - TARGET_DESCRIPTION "QML Cache Generator" - SOURCES - qmlcachegen.cpp - DEFINES - QT_NO_CAST_FROM_ASCII - QT_NO_CAST_TO_ASCII - PUBLIC_LIBRARIES - Qt::QmlCompilerPrivate - Qt::QmlDevToolsPrivate -) - -#### Keys ignored in scope 1:.:.:qmlcachegen.pro:<TRUE>: -# CMAKE_BIN_DIR = "$$cmakeRelativePath($$[QT_HOST_BINS], $$[QT_INSTALL_PREFIX])" -# QMAKE_SUBSTITUTES = "cmake_config_file" -# QMAKE_TARGET_DESCRIPTION = "QML" "Cache" "Generator" -# _OPTION = "host_build" -# build_integration.files = "qmlcache.prf" "qtquickcompiler.prf" -# build_integration.path = "$$[QT_HOST_DATA]/mkspecs/features" -# cmake_build_integration.files = "$$cmake_config_file.output" -# cmake_build_integration.path = "$$[QT_INSTALL_LIBS]/cmake/Qt6QuickCompiler" -# cmake_config_file.input = "$$PWD/Qt6QuickCompilerConfig.cmake.in" -# cmake_config_file.output = "$$MODULE_BASE_OUTDIR/lib/cmake/Qt6QuickCompiler/Qt6QuickCompilerConfig.cmake" - -## Scopes: -##################################################################### - -#### Keys ignored in scope 2:.:.:qmlcachegen.pro:prefix_build: -# INSTALLS = "cmake_build_integration" "build_integration" - -#### Keys ignored in scope 3:.:.:qmlcachegen.pro:else: -# COPIES = "cmake_build_integration" "build_integration" - -#### Keys ignored in scope 4:.:.:qmlcachegen.pro:CMAKE_BIN_DIR___contains___^\\.\\./._x_: -# CMAKE_BIN_DIR = "$$[QT_HOST_BINS]/" -# CMAKE_BIN_DIR_IS_ABSOLUTE = "True" - -#### Keys ignored in scope 5:.:.:qmlcachegen.pro:QMAKE_HOST.os___equals___Windows: -# CMAKE_BIN_SUFFIX = ".exe" diff --git a/tools/qmlcachegen/CMakeLists.txt b/tools/qmlcachegen/CMakeLists.txt index e0ae4c4092..3f58390f2d 100644 --- a/tools/qmlcachegen/CMakeLists.txt +++ b/tools/qmlcachegen/CMakeLists.txt @@ -1,3 +1,6 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + # Generated from qmlcachegen.pro. ##################################################################### @@ -8,15 +11,16 @@ qt_get_tool_target_name(target_name qmlcachegen) qt_internal_add_tool(${target_name} TARGET_DESCRIPTION "QML Cache Generator" TOOLS_TARGET Qml # special case + INSTALL_DIR "${INSTALL_LIBEXECDIR}" SOURCES qmlcachegen.cpp DEFINES QT_NO_CAST_FROM_ASCII QT_NO_CAST_TO_ASCII - PUBLIC_LIBRARIES + LIBRARIES Qt::QmlCompilerPrivate - Qt::QmlDevToolsPrivate ) +qt_internal_return_unless_building_tools() #### Keys ignored in scope 1:.:.:qmlcachegen.pro:<TRUE>: # CMAKE_BIN_DIR = "$$cmakeRelativePath($$[QT_HOST_BINS], $$[QT_INSTALL_PREFIX])" diff --git a/tools/qmlcachegen/Qt6QuickCompilerConfig.cmake.in b/tools/qmlcachegen/Qt6QuickCompilerConfig.cmake.in index fe19af78cb..1ed1568de5 100644 --- a/tools/qmlcachegen/Qt6QuickCompilerConfig.cmake.in +++ b/tools/qmlcachegen/Qt6QuickCompilerConfig.cmake.in @@ -1,4 +1,5 @@ -function(QTQUICK_COMPILER_ADD_RESOURCES) - message(WARNING "Use qt6_add_resources instead of qtquick_compiler_add_resources." - "QML and JavaScript files are automatically compiled then.") +function(qtquick_compiler_add_resources) + message(WARNING "Use qt_add_qml_module instead of qtquick_compiler_add_resources." + "QML and JavaScript files are automatically compiled when creating a QML" + " module.") endfunction() diff --git a/tools/qmlcachegen/qmlcache.prf b/tools/qmlcachegen/qmlcache.prf index 537eaf62ea..0f0d291dfd 100644 --- a/tools/qmlcachegen/qmlcache.prf +++ b/tools/qmlcachegen/qmlcache.prf @@ -3,7 +3,7 @@ static { return() } -qtPrepareTool(QML_CACHEGEN, qmlcachegen, _ARCH_CHECK) +qtPrepareLibExecTool(QML_CACHEGEN, qmlcachegen, _ARCH_CHECK) isEmpty(TARGETPATH): error("Must set TARGETPATH (QML import name) for ahead-of-time QML cache generation") diff --git a/tools/qmlcachegen/qmlcachegen.cpp b/tools/qmlcachegen/qmlcachegen.cpp index 6584d76af8..1180931d9f 100644 --- a/tools/qmlcachegen/qmlcachegen.cpp +++ b/tools/qmlcachegen/qmlcachegen.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtQml module 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$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include <QCoreApplication> #include <QStringList> @@ -36,6 +11,8 @@ #include <QSaveFile> #include <QScopedPointer> #include <QScopeGuard> +#include <QLibraryInfo> +#include <QLoggingCategory> #include <private/qqmlirbuilder_p.h> #include <private/qqmljsparser_p.h> @@ -47,14 +24,14 @@ #include <algorithm> -using namespace QQmlJS; +using namespace Qt::Literals::StringLiterals; static bool argumentsFromCommandLineAndFile(QStringList& allArguments, const QStringList &arguments) { allArguments.reserve(arguments.size()); for (const QString &argument : arguments) { // "@file" doesn't start with a '-' so we can't use QCommandLineParser for it - if (argument.startsWith(QLatin1Char('@'))) { + if (argument.startsWith(u'@')) { QString optionsFile = argument; optionsFile.remove(0, 1); if (optionsFile.isEmpty()) { @@ -81,46 +58,52 @@ static bool argumentsFromCommandLineAndFile(QStringList& allArguments, const QSt int main(int argc, char **argv) { // Produce reliably the same output for the same input by disabling QHash's random seeding. - qSetGlobalQHashSeed(0); + QHashSeed::setDeterministicGlobalSeed(); QCoreApplication app(argc, argv); - QCoreApplication::setApplicationName(QStringLiteral("qmlcachegen")); + QCoreApplication::setApplicationName("qmlcachegen"_L1); QCoreApplication::setApplicationVersion(QLatin1String(QT_VERSION_STR)); QCommandLineParser parser; parser.addHelpOption(); parser.addVersionOption(); - QCommandLineOption filterResourceFileOption(QStringLiteral("filter-resource-file"), QCoreApplication::translate("main", "Filter out QML/JS files from a resource file that can be cached ahead of time instead")); + QCommandLineOption bareOption("bare"_L1, QCoreApplication::translate("main", "Do not include default import directories. This may be used to run qmlcachegen on a project using a different Qt version.")); + parser.addOption(bareOption); + QCommandLineOption filterResourceFileOption("filter-resource-file"_L1, QCoreApplication::translate("main", "Filter out QML/JS files from a resource file that can be cached ahead of time instead")); parser.addOption(filterResourceFileOption); - QCommandLineOption resourceFileMappingOption(QStringLiteral("resource-file-mapping"), QCoreApplication::translate("main", "Path from original resource file to new one"), QCoreApplication::translate("main", "old-name:new-name")); + QCommandLineOption resourceFileMappingOption("resource-file-mapping"_L1, QCoreApplication::translate("main", "Path from original resource file to new one"), QCoreApplication::translate("main", "old-name=new-name")); parser.addOption(resourceFileMappingOption); - QCommandLineOption resourceOption(QStringLiteral("resource"), QCoreApplication::translate("main", "Qt resource file that might later contain one of the compiled files"), QCoreApplication::translate("main", "resource-file-name")); + QCommandLineOption resourceOption("resource"_L1, QCoreApplication::translate("main", "Qt resource file that might later contain one of the compiled files"), QCoreApplication::translate("main", "resource-file-name")); parser.addOption(resourceOption); - QCommandLineOption resourcePathOption(QStringLiteral("resource-path"), QCoreApplication::translate("main", "Qt resource file path corresponding to the file being compiled"), QCoreApplication::translate("main", "resource-path")); + QCommandLineOption resourcePathOption("resource-path"_L1, QCoreApplication::translate("main", "Qt resource file path corresponding to the file being compiled"), QCoreApplication::translate("main", "resource-path")); parser.addOption(resourcePathOption); - QCommandLineOption resourceNameOption(QStringLiteral("resource-name"), - QCoreApplication::translate("main", "Required to generate qmlcache_loader without qrc files. This is the name of the Qt resource the input files belong to."), - QCoreApplication::translate("main", "compiled-file-list")); + QCommandLineOption resourceNameOption("resource-name"_L1, QCoreApplication::translate("main", "Required to generate qmlcache_loader without qrc files. This is the name of the Qt resource the input files belong to."), QCoreApplication::translate("main", "compiled-file-list")); parser.addOption(resourceNameOption); - QCommandLineOption directCallsOption(QStringLiteral("direct-calls"), QCoreApplication::translate("main", "This option is ignored.")); + QCommandLineOption directCallsOption("direct-calls"_L1, QCoreApplication::translate("main", "This option is ignored.")); directCallsOption.setFlags(QCommandLineOption::HiddenFromHelp); parser.addOption(directCallsOption); - QCommandLineOption qmlJSRuntimeOption(QStringLiteral("qmljs-runtime"), QCoreApplication::translate("main", "This option is ignored.")); - qmlJSRuntimeOption.setFlags(QCommandLineOption::HiddenFromHelp); - parser.addOption(qmlJSRuntimeOption); - QCommandLineOption includesOption(QStringLiteral("i"), QCoreApplication::translate("main", "This option is ignored."), QCoreApplication::translate("main", "ignored file")); - includesOption.setFlags(QCommandLineOption::HiddenFromHelp); - parser.addOption(includesOption); - QCommandLineOption importPathOption(QStringLiteral("I"), QCoreApplication::translate("main", "This option is ignored."), QCoreApplication::translate("main", "ignored path")); - importPathOption.setFlags(QCommandLineOption::HiddenFromHelp); + QCommandLineOption staticOption("static"_L1, QCoreApplication::translate("main", "This option is ignored.")); + staticOption.setFlags(QCommandLineOption::HiddenFromHelp); + parser.addOption(staticOption); + QCommandLineOption importsOption("i"_L1, QCoreApplication::translate("main", "Import extra qmldir"), QCoreApplication::translate("main", "qmldir file")); + parser.addOption(importsOption); + QCommandLineOption importPathOption("I"_L1, QCoreApplication::translate("main", "Look for QML modules in specified directory"), QCoreApplication::translate("main", "import directory")); parser.addOption(importPathOption); + QCommandLineOption onlyBytecode("only-bytecode"_L1, QCoreApplication::translate("main", "Generate only byte code for bindings and functions, no C++ code")); + parser.addOption(onlyBytecode); + QCommandLineOption verboseOption("verbose"_L1, QCoreApplication::translate("main", "Output compile warnings")); + parser.addOption(verboseOption); + QCommandLineOption warningsAreErrorsOption("warnings-are-errors"_L1, QCoreApplication::translate("main", "Treat warnings as errors")); + parser.addOption(warningsAreErrorsOption); - QCommandLineOption outputFileOption(QStringLiteral("o"), QCoreApplication::translate("main", "Output file name"), QCoreApplication::translate("main", "file name")); + QCommandLineOption validateBasicBlocksOption("validate-basic-blocks"_L1, QCoreApplication::translate("main", "Performs checks on the basic blocks of a function compiled ahead of time to validate its structure and coherence")); + parser.addOption(validateBasicBlocksOption); + + QCommandLineOption outputFileOption("o"_L1, QCoreApplication::translate("main", "Output file name"), QCoreApplication::translate("main", "file name")); parser.addOption(outputFileOption); - parser.addPositionalArgument(QStringLiteral("[qml file]"), - QStringLiteral("QML source file to generate cache for.")); + parser.addPositionalArgument("[qml file]"_L1, "QML source file to generate cache for."_L1); parser.setSingleDashWordOptionMode(QCommandLineParser::ParseAsLongOptions); @@ -142,9 +125,9 @@ int main(int argc, char **argv) if (parser.isSet(outputFileOption)) outputFileName = parser.value(outputFileOption); - if (outputFileName.endsWith(QLatin1String(".cpp"))) { + if (outputFileName.endsWith(".cpp"_L1)) { target = GenerateCpp; - if (outputFileName.endsWith(QLatin1String("qmlcache_loader.cpp"))) + if (outputFileName.endsWith("qmlcache_loader.cpp"_L1)) target = GenerateLoader; } @@ -154,14 +137,14 @@ int main(int argc, char **argv) const QStringList sources = parser.positionalArguments(); if (sources.isEmpty()){ parser.showHelp(); - } else if (sources.count() > 1 && (target != GenerateLoader && target != GenerateLoaderStandAlone)) { - fprintf(stderr, "%s\n", qPrintable(QStringLiteral("Too many input files specified: '") + sources.join(QStringLiteral("' '")) + QLatin1Char('\''))); + } else if (sources.size() > 1 && (target != GenerateLoader && target != GenerateLoaderStandAlone)) { + fprintf(stderr, "%s\n", qPrintable("Too many input files specified: '"_L1 + sources.join("' '"_L1) + u'\'')); return EXIT_FAILURE; } const QString inputFile = !sources.isEmpty() ? sources.first() : QString(); if (outputFileName.isEmpty()) - outputFileName = inputFile + QLatin1Char('c'); + outputFileName = inputFile + u'c'; if (parser.isSet(filterResourceFileOption)) return qRelocateResourceFile(inputFile, outputFileName); @@ -173,7 +156,7 @@ int main(int argc, char **argv) if (!qQmlJSGenerateLoader( mapper.resourcePaths(QQmlJSResourceFileMapper::allQmlJSFilter()), outputFileName, parser.values(resourceFileMappingOption), &error.message)) { - error.augment(QLatin1String("Error generating loader stub: ")).print(); + error.augment("Error generating loader stub: "_L1).print(); return EXIT_FAILURE; } return EXIT_SUCCESS; @@ -183,7 +166,7 @@ int main(int argc, char **argv) QQmlJSCompileError error; if (!qQmlJSGenerateLoader(sources, outputFileName, parser.values(resourceNameOption), &error.message)) { - error.augment(QLatin1String("Error generating loader stub: ")).print(); + error.augment("Error generating loader stub: "_L1).print(); return EXIT_FAILURE; } return EXIT_SUCCESS; @@ -191,41 +174,39 @@ int main(int argc, char **argv) QString inputFileUrl = inputFile; QQmlJSSaveFunction saveFunction; - if (target == GenerateCpp) { - QQmlJSResourceFileMapper fileMapper(parser.values(resourceOption)); - QString inputResourcePath = parser.value(resourcePathOption); - - // If the user didn't specify the resource path corresponding to the file on disk being - // compiled, try to determine it from the resource file, if one was supplied. - if (inputResourcePath.isEmpty()) { - const QStringList resourcePaths = fileMapper.resourcePaths( - QQmlJSResourceFileMapper::localFileFilter(inputFile)); - if (resourcePaths.isEmpty()) { - fprintf(stderr, "No resource path for file: %s\n", qPrintable(inputFile)); - return EXIT_FAILURE; - } - - if (resourcePaths.size() != 1) { - fprintf(stderr, "Multiple resource paths for file %s. " - "Use the --%s option to disambiguate:\n", - qPrintable(inputFile), - qPrintable(resourcePathOption.names().first())); - for (const QString &resourcePath: resourcePaths) - fprintf(stderr, "\t%s\n", qPrintable(resourcePath)); - return EXIT_FAILURE; - } + QQmlJSResourceFileMapper fileMapper(parser.values(resourceOption)); + QString inputResourcePath = parser.value(resourcePathOption); + + // If the user didn't specify the resource path corresponding to the file on disk being + // compiled, try to determine it from the resource file, if one was supplied. + if (inputResourcePath.isEmpty()) { + const QStringList resourcePaths = fileMapper.resourcePaths( + QQmlJSResourceFileMapper::localFileFilter(inputFile)); + if (target == GenerateCpp && resourcePaths.isEmpty()) { + fprintf(stderr, "No resource path for file: %s\n", qPrintable(inputFile)); + return EXIT_FAILURE; + } + if (resourcePaths.size() == 1) { inputResourcePath = resourcePaths.first(); + } else if (target == GenerateCpp) { + fprintf(stderr, "Multiple resource paths for file %s. " + "Use the --%s option to disambiguate:\n", + qPrintable(inputFile), + qPrintable(resourcePathOption.names().first())); + for (const QString &resourcePath: resourcePaths) + fprintf(stderr, "\t%s\n", qPrintable(resourcePath)); + return EXIT_FAILURE; } + } - inputFileUrl = QStringLiteral("qrc://") + inputResourcePath; - + if (target == GenerateCpp) { + inputFileUrl = "qrc://"_L1 + inputResourcePath; saveFunction = [inputResourcePath, outputFileName]( const QV4::CompiledData::SaveableUnitPointer &unit, const QQmlJSAotFunctionMap &aotFunctions, QString *errorString) { - return qSaveQmlJSUnitAsCpp(inputResourcePath, outputFileName, unit, aotFunctions, - errorString); + return qSaveQmlJSUnitAsCpp(inputResourcePath, outputFileName, unit, aotFunctions, errorString); }; } else { @@ -241,20 +222,72 @@ int main(int argc, char **argv) }; } - if (inputFile.endsWith(QLatin1String(".qml"))) { + if (inputFile.endsWith(".qml"_L1)) { QQmlJSCompileError error; - if (!qCompileQmlFile(inputFile, saveFunction, nullptr, &error)) { - error.augment(QLatin1String("Error compiling qml file: ")).print(); - return EXIT_FAILURE; + if (target != GenerateCpp || inputResourcePath.isEmpty() || parser.isSet(onlyBytecode)) { + if (!qCompileQmlFile(inputFile, saveFunction, nullptr, &error, + /* storeSourceLocation */ false)) { + error.augment("Error compiling qml file: "_L1).print(); + return EXIT_FAILURE; + } + } else { + QStringList importPaths; + + if (parser.isSet(resourceOption)) { + importPaths.append("qt-project.org/imports"_L1); + importPaths.append("qt/qml"_L1); + }; + + if (parser.isSet(importPathOption)) + importPaths.append(parser.values(importPathOption)); + + if (!parser.isSet(bareOption)) + importPaths.append(QLibraryInfo::path(QLibraryInfo::QmlImportsPath)); + + QQmlJSImporter importer( + importPaths, parser.isSet(resourceOption) ? &fileMapper : nullptr); + QQmlJSLogger logger; + + // Always trigger the qFatal() on "pragma Strict" violations. + logger.setCategoryLevel(qmlCompiler, QtWarningMsg); + logger.setCategoryIgnored(qmlCompiler, false); + logger.setCategoryFatal(qmlCompiler, true); + + if (!parser.isSet(verboseOption) && !parser.isSet(warningsAreErrorsOption)) + logger.setSilent(true); + + QQmlJSAotCompiler cppCodeGen( + &importer, u':' + inputResourcePath, parser.values(importsOption), &logger); + + if (parser.isSet(validateBasicBlocksOption)) + cppCodeGen.m_flags.setFlag(QQmlJSAotCompiler::ValidateBasicBlocks); + + if (!qCompileQmlFile(inputFile, saveFunction, &cppCodeGen, &error, + /* storeSourceLocation */ true)) { + error.augment("Error compiling qml file: "_L1).print(); + return EXIT_FAILURE; + } + + QList<QQmlJS::DiagnosticMessage> warnings = importer.takeGlobalWarnings(); + + if (!warnings.isEmpty()) { + logger.log("Type warnings occurred while compiling file:"_L1, + qmlImport, QQmlJS::SourceLocation()); + logger.processMessages(warnings, qmlImport); + if (parser.isSet(warningsAreErrorsOption)) + return EXIT_FAILURE; + } } - } else if (inputFile.endsWith(QLatin1String(".js")) || inputFile.endsWith(QLatin1String(".mjs"))) { + } else if (inputFile.endsWith(".js"_L1) || inputFile.endsWith(".mjs"_L1)) { QQmlJSCompileError error; if (!qCompileJSFile(inputFile, inputFileUrl, saveFunction, &error)) { - error.augment(QLatin1String("Error compiling js file: ")).print(); + error.augment("Error compiling js file: "_L1).print(); return EXIT_FAILURE; } } else { fprintf(stderr, "Ignoring %s input file as it is not QML source code - maybe remove from QML_FILES?\n", qPrintable(inputFile)); + if (parser.isSet(warningsAreErrorsOption)) + return EXIT_FAILURE; } return EXIT_SUCCESS; diff --git a/tools/qmlcachegen/qtquickcompiler.prf b/tools/qmlcachegen/qtquickcompiler.prf index d1b7a5aad5..b7413a3de3 100644 --- a/tools/qmlcachegen/qtquickcompiler.prf +++ b/tools/qmlcachegen/qtquickcompiler.prf @@ -10,8 +10,8 @@ if(qtc_run|lupdate_run): return() unset(qt_modules) } -qtPrepareTool(QML_CACHEGEN, qmlcachegen, _FILTER) -qtPrepareTool(QMAKE_RCC, rcc, _DEP) +qtPrepareLibExecTool(QML_CACHEGEN, qmlcachegen, _FILTER) +qtPrepareLibExecTool(QMAKE_RCC, rcc, _DEP) isEmpty(QMLCACHE_DIR): QMLCACHE_DIR = . @@ -77,6 +77,7 @@ for(res, QMLCACHE_RESOURCE_FILES) { defineReplace(qmlCacheOutputName) { name = $$absolute_path($$1, $$OUT_PWD) name = $$relative_path($$name, $$_PRO_FILE_PWD_) + contains(name, ^\\.\\..*): name = $$relative_path($$1, $$OUT_PWD) name = $$replace(name, \\.qml$, _qml) name = $$replace(name, \\.js$, _js) name = $$replace(name, \\.mjs$, _mjs) diff --git a/tools/qmldom/CMakeLists.txt b/tools/qmldom/CMakeLists.txt new file mode 100644 index 0000000000..86e1a10d29 --- /dev/null +++ b/tools/qmldom/CMakeLists.txt @@ -0,0 +1,18 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +##################################################################### +## qmldom Tool: +##################################################################### + +qt_get_tool_target_name(target_name qmldom) +qt_internal_add_tool(${target_name} + TARGET_DESCRIPTION "QML Dom handler" + TOOLS_TARGET Qml # special case + SOURCES + qmldomtool.cpp + LIBRARIES + Qt::CorePrivate + Qt::QmlDomPrivate +) +qt_internal_return_unless_building_tools() diff --git a/tools/qmldom/qmldomtool.cpp b/tools/qmldom/qmldomtool.cpp new file mode 100644 index 0000000000..99c81e0a95 --- /dev/null +++ b/tools/qmldom/qmldomtool.cpp @@ -0,0 +1,324 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include <QtCore/qdebug.h> +#include <QtCore/qfile.h> +#include <QtCore/qfileinfo.h> +#include <QtCore/qcoreapplication.h> +#include <QtCore/qdir.h> +#include <QtCore/QTextStream> +#include <QtCore/QThread> + +#include <QtQmlDom/private/qqmldomtop_p.h> +#include <QtQmlDom/private/qqmldomfilewriter_p.h> +#include <QtQmlDom/private/qqmldomoutwriter_p.h> +#include <QtQmlDom/private/qqmldomelements_p.h> +#include <QtQmlDom/private/qqmldomfieldfilter_p.h> +#include <QtQmlDom/private/qqmldomastdumper_p.h> + +#include <cstdio> +#include <optional> + +#if QT_CONFIG(commandlineparser) +# include <QtCore/qcommandlineparser.h> +#endif + +#include <QtCore/qlibraryinfo.h> +using namespace QQmlJS::Dom; + +namespace tt { +Q_NAMESPACE + +enum class Dependencies { None, Required }; +Q_ENUM_NS(Dependencies); + +}; +using namespace tt; + +int main(int argc, char *argv[]) +{ + FieldFilter filter = FieldFilter::defaultFilter(); + QCoreApplication a(argc, argv); + QCoreApplication::setApplicationName("qmldom"); + QCoreApplication::setApplicationVersion("1.0"); +#if QT_CONFIG(commandlineparser) + QCommandLineParser parser; + parser.setApplicationDescription(QLatin1String("QML dom tool")); + parser.addHelpOption(); + parser.addVersionOption(); + + QCommandLineOption dumpOption(QStringList() << "d" + << "dump", + QLatin1String("Dumps the code model")); + parser.addOption(dumpOption); + QCommandLineOption reformatOption(QStringList() << "r" + << "reformat", + QLatin1String("reformats the files explicitly passed in")); + parser.addOption(reformatOption); + + QCommandLineOption filterOption( + QStringList() << "f" + << "filter-fields", + QLatin1String("commas separated list of fields to filter out. Prepending a field with " + "'-' skips the field, with '+' it adds it. The field might be prepended " + "by '<type>:' to apply only to elements of that type" + "The default filters are ") + + filter.describeFieldsFilter(), + QLatin1String("fields")); + parser.addOption(filterOption); + + QCommandLineOption qmltypesDirsOption( + QStringList() << "I" + << "qmldirs", + QLatin1String("Look for qmltypes files in specified directory"), + QLatin1String("directory")); + parser.addOption(qmltypesDirsOption); + + QCommandLineOption qmltypesFilesOption(QStringList() << "i" + << "qmltypes", + QLatin1String("Include the specified qmltypes files"), + QLatin1String("qmltypes")); + parser.addOption(qmltypesFilesOption); + + QCommandLineOption pathToDumpOption( + QStringList() << "path-to-dump", + QLatin1String("adds a path to dump. By default the base path of each file is dumped. " + "If any path starts with $ ($env for example) then the environment (and " + "not the loaded files) is used as basis."), + QLatin1String("pathToDump")); + parser.addOption(pathToDumpOption); + + QCommandLineOption dependenciesOption( + QStringList() << "D" + << "dependencies", + QLatin1String("Dependencies to load: none, required, reachable"), + QLatin1String("dependenciesToLoad"), QLatin1String("none")); + parser.addOption(dependenciesOption); + + QCommandLineOption reformatDirOption( + QStringList() << "reformat-dir", + QLatin1String( + "Target directory for the reformatted files, " + "if not given the files are reformatted in place (but backup files are kept)"), + QLatin1String("reformatDir")); + parser.addOption(reformatDirOption); + + QCommandLineOption nBackupsOption( + QStringList() << "backups", + QLatin1String("Number of backup files to generate (default is 2, the oldest, " + "and the last version are kept), "), + QLatin1String("nBackups")); + parser.addOption(nBackupsOption); + + QCommandLineOption dumpAstOption(QStringList() << "dump-ast", + QLatin1String("Dumps the AST of the given QML file.")); + parser.addOption(dumpAstOption); + + parser.addPositionalArgument(QLatin1String("files"), + QLatin1String("list of qml or js files to verify")); + + parser.process(a); + + const auto positionalArguments = parser.positionalArguments(); + if (positionalArguments.isEmpty()) { + parser.showHelp(-1); + } + + if (parser.isSet(filterOption)) { + qDebug() << "filters: " << parser.values(filterOption); + for (const QString &fFields : parser.values(filterOption)) { + if (!filter.addFilter(fFields)) { + return 1; + } + } + filter.setFiltred(); + } + + std::optional<DomType> fileType; + if (parser.isSet(reformatOption)) + fileType = DomType::QmlFile; + + Dependencies dep = Dependencies::None; + for (const QString &depName : parser.values(dependenciesOption)) { + QMetaEnum metaEnum = QMetaEnum::fromType<Dependencies>(); + bool found = false; + for (int i = 0; i < metaEnum.keyCount(); ++i) { + if (QLatin1String(metaEnum.key(i)).compare(depName, Qt::CaseInsensitive) == 0) { + found = true; + dep = Dependencies(metaEnum.value(i)); + } + } + if (!found) { + QStringList values; + for (int i = 0; i < metaEnum.keyCount(); ++i) + values.append(QString::fromUtf8(metaEnum.key(i)).toLower()); + qDebug().noquote() << "Invalid dependencies argument, expected one of " + << values.join(QLatin1Char(',')); + return 1; + } + } + + int nBackups = 2; + if (parser.isSet(nBackupsOption)) { + bool intOk; + nBackups = parser.value(nBackupsOption).toInt(&intOk); + if (!intOk) { + qDebug() << "expected an integer giving the number of backups after --backups, not " + << parser.value(nBackupsOption); + } + } + + QList<Path> pathsToDump; + for (const QString &pStr : parser.values(pathToDumpOption)) { + pathsToDump.append(Path::fromString(pStr)); + } + if (pathsToDump.isEmpty()) + pathsToDump.append(Path()); + + // use host qml import path as a sane default if nothing else has been provided + QStringList qmltypeDirs = parser.isSet(qmltypesDirsOption) + ? parser.values(qmltypesDirsOption) + : QStringList { QLibraryInfo::path(QLibraryInfo::Qml2ImportsPath) }; + + if (!parser.isSet(qmltypesFilesOption)) + qmltypeDirs << "."; + + QStringList qmltypeFiles = + parser.isSet(qmltypesFilesOption) ? parser.values(qmltypesFilesOption) : QStringList {}; +#else + QStringList qmltypeDirs {}; + QStringList qmltypeFiles {}; +#endif + + { + QDebug dbg = qDebug(); + dbg << "dirs:\n"; + for (const QString &d : std::as_const(qmltypeDirs)) + dbg << " '" << d << "'\n"; + dbg << "files:\n"; + for (const QString &f : std::as_const(positionalArguments)) + dbg << " '" << f << "'\n"; + dbg << "fieldFilter: " << filter.describeFieldsFilter(); + dbg << "\n"; + } + DomEnvironment::Options options = DomEnvironment::Option::SingleThreaded; + if (dep == Dependencies::None) + options = options | DomEnvironment::Option::NoDependencies; + std::shared_ptr<DomEnvironment> envPtr(new DomEnvironment(qmltypeDirs, options)); + DomItem env(envPtr); + qDebug() << "will load\n"; + if (dep != Dependencies::None) + envPtr->loadBuiltins(); + QList<DomItem> loadedFiles(positionalArguments.size()); + qsizetype iPos = 0; + for (const QString &s : std::as_const(positionalArguments)) { + envPtr->loadFile( + FileToLoad::fromFileSystem(envPtr, s), + [&loadedFiles, iPos](Path, const DomItem &, const DomItem &newIt) { + loadedFiles[iPos] = newIt; + }, + fileType); + } + envPtr->loadPendingDependencies(); + bool hadFailures = false; + const qsizetype largestFileSizeToCheck = 32000; + + if (parser.isSet(reformatOption)) { + for (auto &qmlFile : loadedFiles) { + QString qmlFilePath = qmlFile.canonicalFilePath(); + if (qmlFile.internalKind() != DomType::QmlFile) { + qWarning() << "cannot reformat" << qmlFile.internalKindStr() << "(" << qmlFilePath + << ")"; + continue; + } + qDebug() << "reformatting" << qmlFilePath; + FileWriter fw; + LineWriterOptions lwOptions; + WriteOutChecks checks = WriteOutCheck::Default; + if (std::shared_ptr<QmlFile> qmlFilePtr = qmlFile.ownerAs<QmlFile>()) + if (qmlFilePtr->code().size() > largestFileSizeToCheck) + checks = WriteOutCheck::None; + QString target = qmlFilePath; + QString rDir = parser.value(reformatDirOption); + if (!rDir.isEmpty()) { + QFileInfo f(qmlFilePath); + QDir d(rDir); + target = d.filePath(f.fileName()); + } + auto res = qmlFile.writeOut(target, nBackups, lwOptions, &fw, checks); + switch (fw.status) { + case FileWriter::Status::ShouldWrite: + case FileWriter::Status::SkippedDueToFailure: + qWarning() << "failure reformatting " << qmlFilePath; + break; + case FileWriter::Status::DidWrite: + qDebug() << "success"; + break; + case FileWriter::Status::SkippedEqual: + qDebug() << "no change"; + } + hadFailures = hadFailures || !res; + } + } else if (parser.isSet(dumpAstOption)) { + if (pathsToDump.size() > 1) { + qWarning() << "--dump-ast can only be used with a single file"; + return 1; + } + for (auto &fileItem : loadedFiles) { + const auto file = fileItem.fileObject().ownerAs<QmlFile>(); + if (!file) { + qWarning() << "cannot dump AST for" << fileItem.canonicalPath(); + qWarning() << "is it a valid QML file?"; + continue; + } + const QString ast = + QQmlJS::Dom::astNodeDump(file->ast(), AstDumperOption::DumpNode, 1, 0); + QTextStream ts(stdout); + ts << ast << Qt::flush; + } + } else if (parser.isSet(dumpOption) || !parser.isSet(reformatOption) + || !parser.isSet(dumpAstOption)) { + qDebug() << "will dump\n"; + QTextStream ts(stdout); + auto sink = [&ts](QStringView v) { + ts << v; /* ts.flush(); */ + }; + qsizetype iPathToDump = 0; + bool globalPaths = false; + for (const auto &p : pathsToDump) + if (p.headKind() == Path::Kind::Root) + globalPaths = true; + if (globalPaths) + loadedFiles = QList<DomItem>({ env }); + bool dumpDict = pathsToDump.size() > 1 || loadedFiles.size() > 1; + if (dumpDict) + sink(u"{\n"); + while (iPathToDump < pathsToDump.size()) { + for (auto &fileItem : loadedFiles) { + Path p = pathsToDump.at(iPathToDump++ % pathsToDump.size()); + if (dumpDict) { + if (iPathToDump > 1) + sink(u",\n"); + sink(u"\""); + if (fileItem.internalKind() != DomType::DomEnvironment) { + sinkEscaped(sink, fileItem.canonicalFilePath(), + EscapeOptions::NoOuterQuotes); + sink(u"/"); + } + sinkEscaped(sink, p.toString(), EscapeOptions::NoOuterQuotes); + sink(u"\":\n"); + } + fileItem.path(p).dump(sink, 0, filter); + } + } + if (dumpDict) + sink(u"}\n"); + Qt::endl(ts).flush(); + } + for (int i = 0; i < 100; ++i) + QThread::yieldCurrentThread(); // let buggy integrations catch up with the output + // return a.exec(); + return 0; +} + +#include "qmldomtool.moc" diff --git a/tools/qmleasing/Button.qml b/tools/qmleasing/Button.qml index 1131e559ab..6dd71b0594 100644 --- a/tools/qmleasing/Button.qml +++ b/tools/qmleasing/Button.qml @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtQml module 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$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 import QtQuick 2.0 @@ -117,26 +92,16 @@ Item { name: "hovered" PropertyChanges { - target: normalBackground - opacity: 0 - } - - PropertyChanges { - target: hoveredBackground - opacity: 1 + normalBackground.opacity: 0 + hoveredBackground.opacity: 1 } }, State { name: "pressed" PropertyChanges { - target: normalBackground - opacity: 0 - } - - PropertyChanges { - target: pressedBackground - opacity: 1 + normalBackground.opacity: 0 + pressedBackground.opacity: 1 } } ] diff --git a/tools/qmleasing/CMakeLists.txt b/tools/qmleasing/CMakeLists.txt index 7257b7cf6e..7bbb0ce7db 100644 --- a/tools/qmleasing/CMakeLists.txt +++ b/tools/qmleasing/CMakeLists.txt @@ -1,3 +1,6 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + # Generated from qmleasing.pro. ##################################################################### @@ -13,7 +16,7 @@ qt_internal_add_app(qmleasing properties.ui segmentproperties.cpp segmentproperties.h splineeditor.cpp splineeditor.h - PUBLIC_LIBRARIES + LIBRARIES Qt::Gui Qt::Qml Qt::Quick @@ -22,6 +25,8 @@ qt_internal_add_app(qmleasing uic ) +set_target_properties(qmleasing PROPERTIES WIN32_EXECUTABLE FALSE) + # Resources: set(resources_resource_files "Button.qml" diff --git a/tools/qmleasing/main.cpp b/tools/qmleasing/main.cpp index 389503507e..59c34fd845 100644 --- a/tools/qmleasing/main.cpp +++ b/tools/qmleasing/main.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 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$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "mainwindow.h" diff --git a/tools/qmleasing/mainwindow.cpp b/tools/qmleasing/mainwindow.cpp index 679b4c0b91..f38847d107 100644 --- a/tools/qmleasing/mainwindow.cpp +++ b/tools/qmleasing/mainwindow.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 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$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "mainwindow.h" #include "splineeditor.h" @@ -69,7 +44,7 @@ MainWindow::MainWindow(QWidget *parent) : vboxLayout->addWidget(m_placeholder); ui_properties.plainTextEdit->setPlainText(splineEditor->generateCode()); - connect(splineEditor, SIGNAL(easingCurveCodeChanged(QString)), ui_properties.plainTextEdit, SLOT(setPlainText(QString))); + connect(splineEditor, &SplineEditor::easingCurveCodeChanged, ui_properties.plainTextEdit, &QPlainTextEdit::setPlainText); quickView.rootContext()->setContextProperty(QLatin1String("spinBox"), ui_properties.spinBox); @@ -77,7 +52,7 @@ MainWindow::MainWindow(QWidget *parent) : for (const QString &name : presetNames) ui_properties.comboBox->addItem(name); - connect(ui_properties.comboBox, SIGNAL(currentIndexChanged(QString)), splineEditor, SLOT(setPreset(QString))); + connect(ui_properties.comboBox, &QComboBox::currentTextChanged, splineEditor, &SplineEditor::setPreset); splineEditor->setPreset(ui_properties.comboBox->currentText()); @@ -87,7 +62,7 @@ MainWindow::MainWindow(QWidget *parent) : groupBoxLayout->addWidget(splineEditor->pointListWidget()); m_splineEditor = splineEditor; - connect(ui_properties.plainTextEdit, SIGNAL(textChanged()), this, SLOT(textEditTextChanged())); + connect(ui_properties.plainTextEdit, &QPlainTextEdit::textChanged, this, &MainWindow::textEditTextChanged); QDialog* importDialog = new QDialog(this); ui_import.setupUi(importDialog); @@ -95,8 +70,8 @@ MainWindow::MainWindow(QWidget *parent) : ui_import.inSlopeEdit->setValidator(new QDoubleValidator(this)); ui_import.outInfluenceEdit->setValidator(new QDoubleValidator(this)); ui_import.outSlopeEdit->setValidator(new QDoubleValidator(this)); - connect(ui_properties.importButton, SIGNAL(clicked()), importDialog, SLOT(show())); - connect(importDialog, SIGNAL(finished(int)), this, SLOT(importData(int))); + connect(ui_properties.importButton, &QPushButton::clicked, importDialog, &QDialog::show); + connect(importDialog, &QDialog::finished, this, &MainWindow::importData); initQml(); } diff --git a/tools/qmleasing/mainwindow.h b/tools/qmleasing/mainwindow.h index 7ca4dbfd5c..34e3d6365e 100644 --- a/tools/qmleasing/mainwindow.h +++ b/tools/qmleasing/mainwindow.h @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 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$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #ifndef MAINWINDOW_H #define MAINWINDOW_H @@ -40,13 +15,11 @@ class MainWindow : public QMainWindow { Q_OBJECT public: - explicit MainWindow(QWidget *parent = 0); + explicit MainWindow(QWidget *parent = nullptr); void showQuickView(); -signals: - -public slots: +public Q_SLOTS: void textEditTextChanged(); void importData(int result); diff --git a/tools/qmleasing/preview.qml b/tools/qmleasing/preview.qml index 3591684da5..9aa933efa6 100644 --- a/tools/qmleasing/preview.qml +++ b/tools/qmleasing/preview.qml @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtQml module 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$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 import QtQuick 2.0 @@ -100,11 +75,12 @@ Item { name: "moved" PropertyChanges { - target: rectangle - x: 567 - y: 9 - anchors.bottomMargin: 6 - anchors.topMargin: 9 + rectangle { + x: 567 + y: 9 + anchors.bottomMargin: 6 + anchors.topMargin: 9 + } } } ] diff --git a/tools/qmleasing/resources.qrc b/tools/qmleasing/resources.qrc deleted file mode 100644 index c184af4662..0000000000 --- a/tools/qmleasing/resources.qrc +++ /dev/null @@ -1,6 +0,0 @@ -<RCC> - <qresource prefix="/"> - <file>preview.qml</file> - <file>Button.qml</file> - </qresource> -</RCC> diff --git a/tools/qmleasing/segmentproperties.cpp b/tools/qmleasing/segmentproperties.cpp index c61feef9a4..562a241abb 100644 --- a/tools/qmleasing/segmentproperties.cpp +++ b/tools/qmleasing/segmentproperties.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 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$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "segmentproperties.h" #include "splineeditor.h" @@ -43,8 +18,8 @@ SegmentProperties::SegmentProperties(QWidget *parent) : m_ui_pane_c1.smooth->setVisible(false); layout->addWidget(widget); - connect(m_ui_pane_c1.p1_x, SIGNAL(valueChanged(double)), this, SLOT(c1Updated())); - connect(m_ui_pane_c1.p1_y, SIGNAL(valueChanged(double)), this, SLOT(c1Updated())); + connect(m_ui_pane_c1.p1_x, &QDoubleSpinBox::valueChanged, this, &SegmentProperties::c1Updated); + connect(m_ui_pane_c1.p1_y, &QDoubleSpinBox::valueChanged, this, &SegmentProperties::c1Updated); } { QWidget *widget = new QWidget(this); @@ -53,8 +28,8 @@ SegmentProperties::SegmentProperties(QWidget *parent) : m_ui_pane_c2.smooth->setVisible(false); layout->addWidget(widget); - connect(m_ui_pane_c2.p1_x, SIGNAL(valueChanged(double)), this, SLOT(c2Updated())); - connect(m_ui_pane_c2.p1_y, SIGNAL(valueChanged(double)), this, SLOT(c2Updated())); + connect(m_ui_pane_c2.p1_x, &QDoubleSpinBox::valueChanged, this, &SegmentProperties::c2Updated); + connect(m_ui_pane_c2.p1_y, &QDoubleSpinBox::valueChanged, this, &SegmentProperties::c2Updated); } { QWidget *widget = new QWidget(this); @@ -62,9 +37,9 @@ SegmentProperties::SegmentProperties(QWidget *parent) : m_ui_pane_p.label->setText("p1"); layout->addWidget(widget); - connect(m_ui_pane_p.smooth, SIGNAL(toggled(bool)), this, SLOT(pUpdated())); - connect(m_ui_pane_p.p1_x, SIGNAL(valueChanged(double)), this, SLOT(pUpdated())); - connect(m_ui_pane_p.p1_y, SIGNAL(valueChanged(double)), this, SLOT(pUpdated())); + connect(m_ui_pane_p.smooth, &QCheckBox::toggled, this, &SegmentProperties::pUpdated); + connect(m_ui_pane_p.p1_x, &QDoubleSpinBox::valueChanged, this, &SegmentProperties::pUpdated); + connect(m_ui_pane_p.p1_y, &QDoubleSpinBox::valueChanged, this, &SegmentProperties::pUpdated); } } diff --git a/tools/qmleasing/segmentproperties.h b/tools/qmleasing/segmentproperties.h index b6bb6e2a5f..cfdea24e77 100644 --- a/tools/qmleasing/segmentproperties.h +++ b/tools/qmleasing/segmentproperties.h @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 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$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #ifndef SEGMENTPROPERTIES_H #define SEGMENTPROPERTIES_H @@ -38,7 +13,7 @@ class SegmentProperties : public QWidget { Q_OBJECT public: - explicit SegmentProperties(QWidget *parent = 0); + explicit SegmentProperties(QWidget *parent = nullptr); void setSplineEditor(SplineEditor *splineEditor) { m_splineEditor = splineEditor; @@ -53,11 +28,7 @@ public: invalidate(); } -signals: - -public slots: - -private slots: +private Q_SLOTS: void c1Updated(); void c2Updated(); void pUpdated(); diff --git a/tools/qmleasing/splineeditor.cpp b/tools/qmleasing/splineeditor.cpp index 99c161d475..dc71adafa4 100644 --- a/tools/qmleasing/splineeditor.cpp +++ b/tools/qmleasing/splineeditor.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 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$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "splineeditor.h" #include "segmentproperties.h" @@ -194,7 +169,7 @@ void SplineEditor::paintEvent(QPaintEvent *) paintControlPoint(QPointF(0.0, 0.0), &painter, false, true, false, false); paintControlPoint(QPointF(1.0, 1.0), &painter, false, true, false, false); - for (int i = 0; i < m_controlPoints.count() - 1; ++i) + for (int i = 0; i < m_controlPoints.size() - 1; ++i) paintControlPoint(m_controlPoints.at(i), &painter, true, @@ -290,7 +265,7 @@ QHash<QString, QEasingCurve> SplineEditor::presets() const QString SplineEditor::generateCode() { QString s = QLatin1String("["); - for (const QPointF &point : qAsConst(m_controlPoints)) { + for (const QPointF &point : std::as_const(m_controlPoints)) { s += QString::number(point.x(), 'g', 2) + QLatin1Char(',') + QString::number(point.y(), 'g', 3) + QLatin1Char(','); } @@ -369,7 +344,7 @@ void SplineEditor::smoothPoint(int index) before = m_controlPoints.at(index - 3); QPointF after = QPointF(1.0, 1.0); - if ((index + 3) < m_controlPoints.count()) + if ((index + 3) < m_controlPoints.size()) after = m_controlPoints.at(index + 3); QPointF tangent = (after - before) / 6; @@ -379,7 +354,7 @@ void SplineEditor::smoothPoint(int index) if (index > 0) m_controlPoints[index - 1] = thisPoint - tangent; - if (index + 1 < m_controlPoints.count()) + if (index + 1 < m_controlPoints.size()) m_controlPoints[index + 1] = thisPoint + tangent; m_smoothList[index / 3] = true; @@ -397,7 +372,7 @@ void SplineEditor::cornerPoint(int index) before = m_controlPoints.at(index - 3); QPointF after = QPointF(1.0, 1.0); - if ((index + 3) < m_controlPoints.count()) + if ((index + 3) < m_controlPoints.size()) after = m_controlPoints.at(index + 3); QPointF thisPoint = m_controlPoints.at(index); @@ -405,7 +380,7 @@ void SplineEditor::cornerPoint(int index) if (index > 0) m_controlPoints[index - 1] = (before - thisPoint) / 3 + thisPoint; - if (index + 1 < m_controlPoints.count()) + if (index + 1 < m_controlPoints.size()) m_controlPoints[index + 1] = (after - thisPoint) / 3 + thisPoint; m_smoothList[(index) / 3] = false; @@ -437,7 +412,7 @@ void SplineEditor::addPoint(const QPointF point) before = m_controlPoints.at(splitIndex); QPointF after = QPointF(1.0, 1.0); - if ((splitIndex + 3) < m_controlPoints.count()) + if ((splitIndex + 3) < m_controlPoints.size()) after = m_controlPoints.at(splitIndex + 3); if (splitIndex > 0) { @@ -566,7 +541,7 @@ bool SplineEditor::isControlPointSmooth(int i) const if (i == 0) return false; - if (i == m_controlPoints.count() - 1) + if (i == m_controlPoints.size() - 1) return false; if (m_numberOfSegments == 1) @@ -577,7 +552,7 @@ bool SplineEditor::isControlPointSmooth(int i) const if (index == 0) return false; - if (index == m_controlPoints.count() - 1) + if (index == m_controlPoints.size() - 1) return false; return m_smoothList.at(index / 3); @@ -636,7 +611,7 @@ void SplineEditor::mouseMoveEvent(QMouseEvent *e) if ((m_activeControlPoint > 1) && (m_activeControlPoint % 3) == 0) { //right control point m_controlPoints[m_activeControlPoint - 2] -= distance; - } else if ((m_activeControlPoint < (m_controlPoints.count() - 2)) //left control point + } else if ((m_activeControlPoint < (m_controlPoints.size() - 2)) //left control point && (m_activeControlPoint % 3) == 1) { m_controlPoints[m_activeControlPoint + 2] -= distance; } @@ -653,7 +628,7 @@ void SplineEditor::setEasingCurve(const QEasingCurve &easingCurve) m_block = true; m_easingCurve = easingCurve; m_controlPoints = m_easingCurve.toCubicSpline(); - m_numberOfSegments = m_controlPoints.count() / 3; + m_numberOfSegments = m_controlPoints.size() / 3; update(); emit easingCurveChanged(); @@ -677,9 +652,9 @@ void SplineEditor::setEasingCurve(const QString &code) if (code.startsWith(QLatin1Char('[')) && code.endsWith(QLatin1Char(']'))) { const auto cleanCode = QStringView(code).mid(1, code.size() - 2); const auto stringList = cleanCode.split(QLatin1Char(','), Qt::SkipEmptyParts); - if (stringList.count() >= 6 && (stringList.count() % 6 == 0)) { + if (stringList.size() >= 6 && (stringList.size() % 6 == 0)) { QVector<qreal> realList; - realList.reserve(stringList.count()); + realList.reserve(stringList.size()); for (const QStringView &string : stringList) { bool ok; realList.append(string.toDouble(&ok)); @@ -687,14 +662,14 @@ void SplineEditor::setEasingCurve(const QString &code) return; } QVector<QPointF> points; - const int count = realList.count() / 2; + const int count = realList.size() / 2; points.reserve(count); for (int i = 0; i < count; ++i) points.append(QPointF(realList.at(i * 2), realList.at(i * 2 + 1))); if (points.constLast() == QPointF(1.0, 1.0)) { QEasingCurve easingCurve(QEasingCurve::BezierSpline); - for (int i = 0; i < points.count() / 3; ++i) { + for (int i = 0; i < points.size() / 3; ++i) { easingCurve.addCubicBezierSegment(points.at(i * 3), points.at(i * 3 + 1), points.at(i * 3 + 2)); diff --git a/tools/qmleasing/splineeditor.h b/tools/qmleasing/splineeditor.h index 8dd47c3a89..3a1a7da3e9 100644 --- a/tools/qmleasing/splineeditor.h +++ b/tools/qmleasing/splineeditor.h @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 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$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #ifndef SPLINEEDITOR_H #define SPLINEEDITOR_H @@ -46,7 +21,7 @@ class SplineEditor : public QWidget Q_PROPERTY(QEasingCurve easingCurve READ easingCurve WRITE setEasingCurve NOTIFY easingCurveChanged); public: - explicit SplineEditor(QWidget *parent = 0); + explicit SplineEditor(QWidget *parent = nullptr); QString generateCode(); QStringList presetNames() const; QWidget *pointListWidget(); @@ -64,12 +39,12 @@ public: //update(); } -signals: +Q_SIGNALS: void easingCurveChanged(); void easingCurveCodeChanged(const QString &code); -public slots: +public Q_SLOTS: void setEasingCurve(const QEasingCurve &easingCurve); void setPreset(const QString &name); void setEasingCurve(const QString &code); diff --git a/tools/qmlformat/.prev_CMakeLists.txt b/tools/qmlformat/.prev_CMakeLists.txt deleted file mode 100644 index 618e1bbfaa..0000000000 --- a/tools/qmlformat/.prev_CMakeLists.txt +++ /dev/null @@ -1,21 +0,0 @@ -# Generated from qmlformat.pro. - -##################################################################### -## qmlformat Tool: -##################################################################### - -qt_get_tool_target_name(target_name qmlformat) -qt_internal_add_tool(${target_name} - TARGET_DESCRIPTION "QML Formatter" - SOURCES - commentastvisitor.cpp commentastvisitor.h - dumpastvisitor.cpp dumpastvisitor.h - main.cpp - restructureastvisitor.cpp restructureastvisitor.h - PUBLIC_LIBRARIES - Qt::QmlDevToolsPrivate -) - -#### Keys ignored in scope 1:.:.:qmlformat.pro:<TRUE>: -# QMAKE_TARGET_DESCRIPTION = "QML" "Formatter" -# _OPTION = "host_build" diff --git a/tools/qmlformat/CMakeLists.txt b/tools/qmlformat/CMakeLists.txt index e492a3ec56..908901b9f5 100644 --- a/tools/qmlformat/CMakeLists.txt +++ b/tools/qmlformat/CMakeLists.txt @@ -1,3 +1,6 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + # Generated from qmlformat.pro. ##################################################################### @@ -9,13 +12,13 @@ qt_internal_add_tool(${target_name} TARGET_DESCRIPTION "QML Formatter" TOOLS_TARGET Qml # special case SOURCES - commentastvisitor.cpp commentastvisitor.h - dumpastvisitor.cpp dumpastvisitor.h - main.cpp - restructureastvisitor.cpp restructureastvisitor.h - PUBLIC_LIBRARIES - Qt::QmlDevToolsPrivate + qmlformat.cpp + LIBRARIES + Qt::Core + Qt::QmlDomPrivate + Qt::QmlToolingSettingsPrivate ) +qt_internal_return_unless_building_tools() #### Keys ignored in scope 1:.:.:qmlformat.pro:<TRUE>: # QMAKE_TARGET_DESCRIPTION = "QML" "Formatter" diff --git a/tools/qmlformat/commentastvisitor.cpp b/tools/qmlformat/commentastvisitor.cpp deleted file mode 100644 index b8d916d3fb..0000000000 --- a/tools/qmlformat/commentastvisitor.cpp +++ /dev/null @@ -1,285 +0,0 @@ -/**************************************************************************** -** -** 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 "commentastvisitor.h" - -CommentAstVisitor::CommentAstVisitor(QQmlJS::Engine *engine, Node *rootNode) : m_engine(engine) -{ - rootNode->accept(this); - - // Look for complete orphans that have not been attached to *any* node - QVector<Comment> completeOrphans; - - for (const auto &comment : m_engine->comments()) { - if (isCommentAttached(comment)) - continue; - - bool found_orphan = false; - for (const auto &orphanList : orphanComments().values()) { - for (const auto &orphan : orphanList) { - if (orphan.contains(comment)) { - found_orphan = true; - break; - } - } - - if (found_orphan) - break; - } - - if (found_orphan) - continue; - - completeOrphans.append(Comment(m_engine, Comment::Location::Front, {comment})); - } - - m_orphanComments[nullptr] = completeOrphans; -} - -QList<SourceLocation> CommentAstVisitor::findCommentsInLine(quint32 line, bool includePrevious) const -{ - QList<SourceLocation> results; - if (line == 0) - return results; - - for (const auto &location : m_engine->comments()) { - Comment comment(m_engine, Comment::Location::Front, { location }); - if (line < location.startLine || line > comment.endLine()) - continue; - - if (isCommentAttached(location)) - continue; - - results.append(location); - - if (includePrevious) { - // See if we can find any more comments above this one - auto previous = findCommentsInLine(location.startLine - 1, true); - - // Iterate it in reverse to restore the correct order - for (auto it = previous.rbegin(); it != previous.rend(); it++) { - results.prepend(*it); - } - } - - break; - } - - return results; -} - -bool CommentAstVisitor::isCommentAttached(const SourceLocation &location) const -{ - for (const auto &value : m_attachedComments.values()) { - if (value.contains(location)) - return true; - } - - for (const auto &value : m_listItemComments.values()) { - if (value.contains(location)) - return true; - } - - // If a comment is already marked as an orphan of a Node that counts as attached too. - for (const auto &orphanList : m_orphanComments.values()) { - for (const auto &value : orphanList) { - if (value.contains(location)) - return true; - } - } - - return false; -} - -Comment CommentAstVisitor::findComment(SourceLocation first, SourceLocation last, - int locations) const -{ - if (locations & Comment::Location::Front) { - quint32 searchAt = first.startLine - 1; - - const auto comments = findCommentsInLine(searchAt, true); - if (!comments.isEmpty()) - return Comment(m_engine, Comment::Location::Front, comments); - } - - if (locations & Comment::Location::Front_Inline) { - quint32 searchAt = first.startLine; - - const auto comments = findCommentsInLine(searchAt); - if (!comments.isEmpty()) - return Comment(m_engine, Comment::Location::Front_Inline, comments); - } - - if (locations & Comment::Location::Back_Inline) { - quint32 searchAt = last.startLine; - - const auto comments = findCommentsInLine(searchAt); - if (!comments.isEmpty()) - return Comment(m_engine, Comment::Location::Back_Inline, comments); - } - - if (locations & Comment::Location::Back) { - quint32 searchAt = last.startLine + 1; - - const auto comments = findCommentsInLine(searchAt); - if (!comments.isEmpty()) - return Comment(m_engine, Comment::Location::Back, comments); - } - - return Comment(); - -} - -Comment CommentAstVisitor::findComment(Node *node, int locations) const -{ - return findComment(node->firstSourceLocation(), node->lastSourceLocation(), locations); -} - -QVector<Comment> CommentAstVisitor::findOrphanComments(Node *node) const -{ - QVector<Comment> comments; - - for (auto &comment : m_engine->comments()) { - if (isCommentAttached(comment)) - continue; - - if (comment.begin() <= node->firstSourceLocation().begin() - || comment.end() > node->lastSourceLocation().end()) { - continue; - } - - comments.append(Comment(m_engine, Comment::Location::Front, {comment})); - } - - return comments; -} - -void CommentAstVisitor::attachComment(Node *node, int locations) -{ - auto comment = findComment(node, locations); - - if (comment.isValid()) - m_attachedComments[node] = comment; -} - -bool CommentAstVisitor::visit(UiScriptBinding *node) -{ - attachComment(node); - return true; -} - -bool CommentAstVisitor::visit(StatementList *node) -{ - for (auto *item = node; item != nullptr; item = item->next) - attachComment(item->statement, Comment::Front | Comment::Back_Inline); - return true; -} - -void CommentAstVisitor::endVisit(StatementList *node) -{ - m_orphanComments[node] = findOrphanComments(node); -} - -bool CommentAstVisitor::visit(UiObjectBinding *node) -{ - attachComment(node, Comment::Front | Comment::Front_Inline | Comment::Back); - return true; -} - -bool CommentAstVisitor::visit(UiObjectDefinition *node) -{ - attachComment(node, Comment::Front | Comment::Front_Inline | Comment::Back); - return true; -} - -void CommentAstVisitor::endVisit(UiObjectDefinition *node) -{ - m_orphanComments[node] = findOrphanComments(node); -} - -bool CommentAstVisitor::visit(UiArrayBinding *node) -{ - attachComment(node); - return true; -} - -void CommentAstVisitor::endVisit(UiArrayBinding *node) -{ - m_orphanComments[node] = findOrphanComments(node); -} - -bool CommentAstVisitor::visit(UiEnumDeclaration *node) -{ - attachComment(node); - return true; -} - -void CommentAstVisitor::endVisit(UiEnumDeclaration *node) -{ - m_orphanComments[node] = findOrphanComments(node); -} - -bool CommentAstVisitor::visit(UiEnumMemberList *node) -{ - for (auto *item = node; item != nullptr; item = item->next) { - auto comment = findComment(item->memberToken, - item->valueToken.isValid() ? item->valueToken : item->memberToken, - Comment::Front | Comment::Back_Inline); - - if (comment.isValid()) - m_listItemComments[item->memberToken.begin()] = comment; - } - - m_orphanComments[node] = findOrphanComments(node); - - return true; -} - -bool CommentAstVisitor::visit(UiPublicMember *node) -{ - attachComment(node); - return true; -} - -bool CommentAstVisitor::visit(FunctionDeclaration *node) -{ - attachComment(node); - return true; -} - -bool CommentAstVisitor::visit(UiImport *node) -{ - attachComment(node); - return true; -} - -bool CommentAstVisitor::visit(UiPragma *node) -{ - attachComment(node); - return true; -} diff --git a/tools/qmlformat/commentastvisitor.h b/tools/qmlformat/commentastvisitor.h deleted file mode 100644 index 09bc786985..0000000000 --- a/tools/qmlformat/commentastvisitor.h +++ /dev/null @@ -1,143 +0,0 @@ -/**************************************************************************** -** -** 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 COMMENTASTVISITOR_H -#define COMMENTASTVISITOR_H - -#include <QtQml/private/qqmljsastvisitor_p.h> -#include <QtQml/private/qqmljsast_p.h> -#include <QtQml/private/qqmljsengine_p.h> - -#include <QHash> -#include <QString> -#include <QVector> - -using namespace QQmlJS::AST; -using namespace QQmlJS; - -struct Comment -{ - enum Location : int - { - Front = 1, - Front_Inline = Front << 1, - Back = Front_Inline << 1, - Back_Inline = Back << 1, - DefaultLocations = Front | Back_Inline, - AllLocations = Front | Back | Front_Inline | Back_Inline - } m_location = Front; - - Comment() = default; - Comment(const QQmlJS::Engine *engine, Location location, QList<SourceLocation> srcLocations) - : m_location(location), m_srcLocations(srcLocations) { - for (const auto& srcLoc : srcLocations) { - m_text += engine->code().mid(static_cast<int>(srcLoc.begin()), - static_cast<int>(srcLoc.end() - srcLoc.begin())) + "\n"; - } - - m_text.chop(1); - } - - QList<SourceLocation> m_srcLocations; - - bool hasSheBang() const { return !m_srcLocations.isEmpty() && m_srcLocations.first().begin() == 0; } - bool isValid() const { return !m_srcLocations.isEmpty(); } - bool isMultiline() const { return m_text.contains("\n"); } - bool isSyntheticMultiline() const { return m_srcLocations.size() > 1; } - - bool contains(const SourceLocation& location) const { - for (const SourceLocation& srcLoc : m_srcLocations) { - if (srcLoc.begin() == location.begin() && srcLoc.end() == location.end()) - return true; - } - - return false; - } - - quint32 endLine() const - { - if (isSyntheticMultiline() || !isValid()) - return 0; - - return m_srcLocations[0].startLine + m_text.count(QLatin1Char('\n')); - } - - QString m_text; -}; - -class CommentAstVisitor : protected Visitor -{ -public: - CommentAstVisitor(QQmlJS::Engine *engine, Node *rootNode); - - void throwRecursionDepthError() override {} - - const QHash<Node *, Comment> attachedComments() const { return m_attachedComments; } - const QHash<quint32, Comment> listComments() const { return m_listItemComments; } - const QHash<Node *, QVector<Comment>> orphanComments() const { return m_orphanComments; } - - bool visit(UiScriptBinding *node) override; - bool visit(UiObjectBinding *node) override; - - bool visit(UiArrayBinding *node) override; - void endVisit(UiArrayBinding *node) override; - - bool visit(UiObjectDefinition *node) override; - void endVisit(UiObjectDefinition *) override; - - bool visit(UiEnumDeclaration *node) override; - void endVisit(UiEnumDeclaration *node) override; - - bool visit(UiEnumMemberList *node) override; - - bool visit(StatementList *node) override; - void endVisit(StatementList *node) override; - - bool visit(UiImport *node) override; - bool visit(UiPragma *node) override; - bool visit(UiPublicMember *node) override; - bool visit(FunctionDeclaration *node) override; -private: - bool isCommentAttached(const SourceLocation& location) const; - - QList<SourceLocation> findCommentsInLine(quint32 line, bool includePrevious = false) const; - - Comment findComment(SourceLocation first, SourceLocation last, - int locations = Comment::DefaultLocations) const; - - Comment findComment(Node *node, int locations = Comment::DefaultLocations) const; - QVector<Comment> findOrphanComments(Node *node) const; - void attachComment(Node *node, int locations = Comment::DefaultLocations); - - QQmlJS::Engine *m_engine; - QHash<Node *, Comment> m_attachedComments; - QHash<quint32, Comment> m_listItemComments; - QHash<Node *, QVector<Comment>> m_orphanComments; -}; - -#endif // COMMENTASTVISITOR_H diff --git a/tools/qmlformat/dumpastvisitor.cpp b/tools/qmlformat/dumpastvisitor.cpp deleted file mode 100644 index 723be4e445..0000000000 --- a/tools/qmlformat/dumpastvisitor.cpp +++ /dev/null @@ -1,1433 +0,0 @@ -/**************************************************************************** -** -** 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 "dumpastvisitor.h" - -#include <QtQml/private/qqmljslexer_p.h> - -DumpAstVisitor::DumpAstVisitor(QQmlJS::Engine *engine, Node *rootNode, CommentAstVisitor *comment, - int indentWidth, DumpAstVisitor::Indentation indentation) - : m_engine(engine), m_comment(comment), m_indentWidth(indentWidth), m_indentation(indentation) -{ - // Add all completely orphaned comments - m_result += getOrphanedComments(nullptr); - - m_scope_properties.push(ScopeProperties {}); - - rootNode->accept(this); - - // We need to get rid of one new-line so our output doesn't append an empty line - m_result.chop(1); - - // Remove trailing whitespace - QStringList lines = m_result.split("\n"); - for (QString& line : lines) { - while (line.endsWith(" ")) - line.chop(1); - } - - m_result = lines.join("\n"); -} - -bool DumpAstVisitor::preVisit(Node *el) -{ - UiObjectMember *m = el->uiObjectMemberCast(); - if (m != 0) - Node::accept(m->annotations, this); - return true; -} - -static QString parseUiQualifiedId(UiQualifiedId *id) -{ - QString name = id->name.toString(); - for (auto *item = id->next; item != nullptr; item = item->next) { - name += "." + item->name; - } - - return name; -} - -static QString operatorToString(int op) -{ - switch (op) - { - case QSOperator::Add: return "+"; - case QSOperator::And: return "&&"; - case QSOperator::InplaceAnd: return "&="; - case QSOperator::Assign: return "="; - case QSOperator::BitAnd: return "&"; - case QSOperator::BitOr: return "|"; - case QSOperator::BitXor: return "^"; - case QSOperator::InplaceSub: return "-="; - case QSOperator::Div: return "/"; - case QSOperator::InplaceDiv: return "/="; - case QSOperator::Equal: return "=="; - case QSOperator::Exp: return "**"; - case QSOperator::InplaceExp: return "**="; - case QSOperator::Ge: return ">="; - case QSOperator::Gt: return ">"; - case QSOperator::In: return "in"; - case QSOperator::InplaceAdd: return "+="; - case QSOperator::InstanceOf: return "instanceof"; - case QSOperator::Le: return "<="; - case QSOperator::LShift: return "<<"; - case QSOperator::InplaceLeftShift: return "<<="; - case QSOperator::Lt: return "<"; - case QSOperator::Mod: return "%"; - case QSOperator::InplaceMod: return "%="; - case QSOperator::Mul: return "*"; - case QSOperator::InplaceMul: return "*="; - case QSOperator::NotEqual: return "!="; - case QSOperator::Or: return "||"; - case QSOperator::InplaceOr: return "|="; - case QSOperator::RShift: return ">>"; - case QSOperator::InplaceRightShift: return ">>="; - case QSOperator::StrictEqual: return "==="; - case QSOperator::StrictNotEqual: return "!=="; - case QSOperator::Sub: return "-"; - case QSOperator::URShift: return ">>>"; - case QSOperator::InplaceURightShift: return ">>>="; - case QSOperator::InplaceXor: return "^="; - case QSOperator::As: return "as"; - case QSOperator::Coalesce: return "??"; - case QSOperator::Invalid: - default: - return "INVALID"; - } -} - -QString DumpAstVisitor::formatComment(const Comment &comment) const -{ - QString result; - - bool useMultilineComment = comment.isMultiline() && !comment.isSyntheticMultiline(); - - if (useMultilineComment) - result += "/*"; - else if (!comment.hasSheBang()) - result += "//"; - - result += comment.m_text; - - if (comment.isSyntheticMultiline()) - result = result.replace("\n","\n" + formatLine("//", false)); - - if (comment.m_location == Comment::Location::Back_Inline) - result.prepend(" "); - - if (useMultilineComment) - result += "*/"; - - return result; -} - -QString DumpAstVisitor::getComment(Node *node, Comment::Location location) const -{ - const auto& comments = m_comment->attachedComments(); - if (!comments.contains(node)) - return ""; - - auto comment = comments[node]; - - if (comment.m_location != location) - return ""; - - return formatComment(comment); -} - -QString DumpAstVisitor::getListItemComment(SourceLocation srcLocation, - Comment::Location location) const { - const auto& comments = m_comment->listComments(); - - if (!comments.contains(srcLocation.begin())) - return ""; - - auto comment = comments[srcLocation.begin()]; - - if (comment.m_location != location) - return ""; - - return formatComment(comment); -} - -QString DumpAstVisitor::getOrphanedComments(Node *node) const { - const auto& orphans = m_comment->orphanComments()[node]; - - if (orphans.size() == 0) - return ""; - - QString result = ""; - - for (const Comment& orphan : orphans) { - result += formatLine(formatComment(orphan)); - } - - result += "\n"; - - return result; -} - -QString DumpAstVisitor::parseArgumentList(ArgumentList *list) -{ - QString result = ""; - - for (auto *item = list; item != nullptr; item = item->next) - result += parseExpression(item->expression) + (item->next != nullptr ? ", " : ""); - - return result; -} - -QString DumpAstVisitor::parseUiParameterList(UiParameterList *list) { - QString result = ""; - - for (auto *item = list; item != nullptr; item = item->next) - result += parseUiQualifiedId(item->type) + " " + item->name + (item->next != nullptr ? ", " : ""); - - return result; -} - -QString DumpAstVisitor::parsePatternElement(PatternElement *element, bool scope) -{ - switch (element->type) - { - case PatternElement::Literal: - return parseExpression(element->initializer); - case PatternElement::Binding: { - QString result = ""; - QString expr = parseExpression(element->initializer); - - if (scope) { - switch (element->scope) { - case VariableScope::NoScope: - break; - case VariableScope::Let: - result = "let "; - break; - case VariableScope::Const: - result = "const "; - break; - case VariableScope::Var: - result = "var "; - break; - } - } - - if (element->bindingIdentifier.isEmpty()) - result += parseExpression(element->bindingTarget); - else - result += element->bindingIdentifier.toString(); - - if (element->typeAnnotation != nullptr) - result += ": " + parseType(element->typeAnnotation->type); - - if (!expr.isEmpty()) - result += " = "+expr; - - return result; - } - default: - m_error = true; - return "pe_unknown"; - } -} - -static QString escapeString(QString string) -{ - // Handle escape sequences - string = string.replace("\r", "\\r").replace("\n", "\\n").replace("\t", "\\t") - .replace("\b","\\b").replace("\v", "\\v").replace("\f", "\\f"); - - // Escape backslash - string = string.replace("\\", "\\\\"); - - // Escape " - string = string.replace("\"", "\\\""); - - return "\"" + string + "\""; -} - -QString DumpAstVisitor::parsePatternElementList(PatternElementList *list) -{ - QString result = ""; - - for (auto *item = list; item != nullptr; item = item->next) - result += parsePatternElement(item->element) + (item->next != nullptr ? ", " : ""); - - return result; -} - -QString DumpAstVisitor::parseFormalParameterList(FormalParameterList *list) -{ - QString result = ""; - - for (auto *item = list; item != nullptr; item = item->next) - result += parsePatternElement(item->element) + (item->next != nullptr ? ", " : ""); - - return result; -} - -QString DumpAstVisitor::parsePatternProperty(PatternProperty *property) -{ - switch (property->type) { - case PatternElement::Getter: - return "get "+parseFunctionExpression(cast<FunctionExpression *>(property->initializer), true); - case PatternElement::Setter: - return "set "+parseFunctionExpression(cast<FunctionExpression *>(property->initializer), true); - default: - if (property->name->kind == Node::Kind_ComputedPropertyName) { - return "["+parseExpression(cast<ComputedPropertyName *>(property->name)->expression)+"]: "+parsePatternElement(property, false); - } else { - return escapeString(property->name->asString())+": "+parsePatternElement(property, false); - } - } -} - -QString DumpAstVisitor::parsePatternPropertyList(PatternPropertyList *list) -{ - QString result = ""; - - for (auto *item = list; item != nullptr; item = item->next) { - result += formatLine(parsePatternProperty(item->property) + (item->next != nullptr ? "," : "")); - } - - return result; -} - -QString DumpAstVisitor::parseFunctionExpression(FunctionExpression *functExpr, bool omitFunction) -{ - m_indentLevel++; - QString result; - bool hasBraces = true; - - if (!functExpr->isArrowFunction) { - result += omitFunction ? "" : "function"; - - if (functExpr->isGenerator) - result += "*"; - - if (!functExpr->name.isEmpty()) - result += (omitFunction ? "" : " ") + functExpr->name; - - result += "("+parseFormalParameterList(functExpr->formals)+")"; - - if (functExpr->typeAnnotation != nullptr) - result += " : " + parseType(functExpr->typeAnnotation->type); - - result += " {\n" + parseStatementList(functExpr->body); - } else { - result += "("+parseFormalParameterList(functExpr->formals)+")"; - - if (functExpr->typeAnnotation != nullptr) - result += " : " + parseType(functExpr->typeAnnotation->type); - - result += " => "; - - if (functExpr->body == nullptr) { - result += "{}"; - } else if (functExpr->body->next == nullptr && functExpr->body->statement->kind == Node::Kind_ReturnStatement) { - m_indentLevel--; - result += parseExpression(cast<ReturnStatement *>(functExpr->body->statement)->expression); - hasBraces = false; - } else { - result += "{\n" + parseStatementList(functExpr->body); - } - } - - if (hasBraces) { - m_indentLevel--; - result += formatLine("}", false); - } - - return result; - -} - -QString DumpAstVisitor::parseType(Type *type) { - QString result = parseUiQualifiedId(type->typeId); - - if (type->typeArguments != nullptr) { - TypeArgumentList *list = cast<TypeArgumentList *>(type->typeArguments); - - result += "<"; - - for (auto *item = list; item != nullptr; item = item->next) { - result += parseType(item->typeId) + (item->next != nullptr ? ", " : ""); - } - - result += ">"; - } - - return result; -} - -QString DumpAstVisitor::parseExpression(ExpressionNode *expression) -{ - if (expression == nullptr) - return ""; - - switch (expression->kind) - { - case Node::Kind_ArrayPattern: - return "["+parsePatternElementList(cast<ArrayPattern *>(expression)->elements)+"]"; - case Node::Kind_IdentifierExpression: - return cast<IdentifierExpression*>(expression)->name.toString(); - case Node::Kind_FieldMemberExpression: { - auto *fieldMemberExpr = cast<FieldMemberExpression *>(expression); - QString result = parseExpression(fieldMemberExpr->base); - - // If we're operating on a numeric literal, always put it in braces - if (fieldMemberExpr->base->kind == Node::Kind_NumericLiteral) - result = "(" + result + ")"; - - result += "." + fieldMemberExpr->name.toString(); - - return result; - } - case Node::Kind_ArrayMemberExpression: { - auto *arrayMemberExpr = cast<ArrayMemberExpression *>(expression); - return parseExpression(arrayMemberExpr->base) - + "[" + parseExpression(arrayMemberExpr->expression) + "]"; - } - case Node::Kind_NestedExpression: - return "("+parseExpression(cast<NestedExpression *>(expression)->expression)+")"; - case Node::Kind_TrueLiteral: - return "true"; - case Node::Kind_FalseLiteral: - return "false"; - case Node::Kind_FunctionExpression: - { - auto *functExpr = cast<FunctionExpression *>(expression); - return parseFunctionExpression(functExpr); - } - case Node::Kind_NullExpression: - return "null"; - case Node::Kind_ThisExpression: - return "this"; - case Node::Kind_PostIncrementExpression: - return parseExpression(cast<PostIncrementExpression *>(expression)->base)+"++"; - case Node::Kind_PreIncrementExpression: - return "++"+parseExpression(cast<PreIncrementExpression *>(expression)->expression); - case Node::Kind_PostDecrementExpression: - return parseExpression(cast<PostDecrementExpression *>(expression)->base)+"--"; - case Node::Kind_PreDecrementExpression: - return "--"+parseExpression(cast<PreDecrementExpression *>(expression)->expression); - case Node::Kind_NumericLiteral: - return QString::number(cast<NumericLiteral *>(expression)->value); - case Node::Kind_TemplateLiteral: { - auto firstSrcLoc = cast<TemplateLiteral *>(expression)->firstSourceLocation(); - auto lastSrcLoc = cast<TemplateLiteral *>(expression)->lastSourceLocation(); - return m_engine->code().mid(static_cast<int>(firstSrcLoc.begin()), - static_cast<int>(lastSrcLoc.end() - firstSrcLoc.begin())); - } - case Node::Kind_StringLiteral: { - auto srcLoc = cast<StringLiteral *>(expression)->firstSourceLocation(); - return m_engine->code().mid(static_cast<int>(srcLoc.begin()), - static_cast<int>(srcLoc.end() - srcLoc.begin())); - } - case Node::Kind_BinaryExpression: { - auto *binExpr = expression->binaryExpressionCast(); - return parseExpression(binExpr->left) + " " + operatorToString(binExpr->op) - + " " + parseExpression(binExpr->right); - } - case Node::Kind_CallExpression: { - auto *callExpr = cast<CallExpression *>(expression); - - return parseExpression(callExpr->base) + "(" + parseArgumentList(callExpr->arguments) + ")"; - } - case Node::Kind_NewExpression: - return "new "+parseExpression(cast<NewExpression *>(expression)->expression); - case Node::Kind_NewMemberExpression: { - auto *newMemberExpression = cast<NewMemberExpression *>(expression); - return "new "+parseExpression(newMemberExpression->base) - + "(" +parseArgumentList(newMemberExpression->arguments)+")"; - } - case Node::Kind_DeleteExpression: - return "delete " + parseExpression(cast<DeleteExpression *>(expression)->expression); - case Node::Kind_VoidExpression: - return "void " + parseExpression(cast<VoidExpression *>(expression)->expression); - case Node::Kind_TypeOfExpression: - return "typeof " + parseExpression(cast<TypeOfExpression *>(expression)->expression); - case Node::Kind_UnaryPlusExpression: - return "+" + parseExpression(cast<UnaryPlusExpression *>(expression)->expression); - case Node::Kind_UnaryMinusExpression: - return "-" + parseExpression(cast<UnaryMinusExpression *>(expression)->expression); - case Node::Kind_NotExpression: - return "!" + parseExpression(cast<NotExpression *>(expression)->expression); - case Node::Kind_TildeExpression: - return "~" + parseExpression(cast<TildeExpression *>(expression)->expression); - case Node::Kind_ConditionalExpression: { - auto *condExpr = cast<ConditionalExpression *>(expression); - - QString result = ""; - - result += parseExpression(condExpr->expression) + " ? "; - result += parseExpression(condExpr->ok) + " : "; - result += parseExpression(condExpr->ko); - - return result; - } - case Node::Kind_YieldExpression: { - auto *yieldExpr = cast<YieldExpression*>(expression); - - QString result = "yield"; - - if (yieldExpr->isYieldStar) - result += "*"; - - if (yieldExpr->expression) - result += " " + parseExpression(yieldExpr->expression); - - return result; - } - case Node::Kind_ObjectPattern: { - auto *objectPattern = cast<ObjectPattern*>(expression); - - if (objectPattern->properties == nullptr) - return "{}"; - - QString result = "{\n"; - - m_indentLevel++; - result += parsePatternPropertyList(objectPattern->properties); - m_indentLevel--; - - result += formatLine("}", false); - - return result; - } - case Node::Kind_Expression: { - auto* expr = cast<Expression*>(expression); - return parseExpression(expr->left)+", "+parseExpression(expr->right); - } - case Node::Kind_TypeExpression: { - auto* type = cast<TypeExpression*>(expression); - return parseType(type->m_type); - } - case Node::Kind_RegExpLiteral: { - auto* regexpLiteral = cast<RegExpLiteral*>(expression); - QString result = "/"+regexpLiteral->pattern+"/"; - - if (regexpLiteral->flags & QQmlJS::Lexer::RegExp_Unicode) - result += "u"; - if (regexpLiteral->flags & QQmlJS::Lexer::RegExp_Global) - result += "g"; - if (regexpLiteral->flags & QQmlJS::Lexer::RegExp_Multiline) - result += "m"; - if (regexpLiteral->flags & QQmlJS::Lexer::RegExp_Sticky) - result += "y"; - if (regexpLiteral->flags & QQmlJS::Lexer::RegExp_IgnoreCase) - result += "i"; - - return result; - } - default: - m_error = true; - return "unknown_expression_"+QString::number(expression->kind); - } -} - -QString DumpAstVisitor::parseVariableDeclarationList(VariableDeclarationList *list) -{ - QString result = ""; - - for (auto *item = list; item != nullptr; item = item->next) { - result += parsePatternElement(item->declaration, (item == list)) - + (item->next != nullptr ? ", " : ""); - } - - return result; -} - -QString DumpAstVisitor::parseCaseBlock(CaseBlock *block) -{ - QString result = "{\n"; - - for (auto *item = block->clauses; item != nullptr; item = item->next) { - result += formatLine("case "+parseExpression(item->clause->expression)+":"); - m_indentLevel++; - result += parseStatementList(item->clause->statements); - m_indentLevel--; - } - - if (block->defaultClause) { - result += formatLine("default:"); - m_indentLevel++; - result += parseStatementList(block->defaultClause->statements); - m_indentLevel--; - } - - result += formatLine("}", false); - - return result; -} - -QString DumpAstVisitor::parseExportSpecifier(ExportSpecifier *specifier) -{ - QString result = specifier->identifier.toString(); - - if (!specifier->exportedIdentifier.isEmpty()) - result += " as " + specifier->exportedIdentifier; - - return result; -} - -QString DumpAstVisitor::parseExportsList(ExportsList *list) -{ - QString result = ""; - - for (auto *item = list; item != nullptr; item = item->next) { - result += formatLine(parseExportSpecifier(item->exportSpecifier) - + (item->next != nullptr ? "," : "")); - } - - return result; -} - -static bool needsSemicolon(int kind) -{ - switch (kind) { - case Node::Kind_ForStatement: - case Node::Kind_ForEachStatement: - case Node::Kind_IfStatement: - case Node::Kind_SwitchStatement: - case Node::Kind_WhileStatement: - case Node::Kind_DoWhileStatement: - case Node::Kind_TryStatement: - case Node::Kind_WithStatement: - return false; - default: - return true; - } -} - -QString DumpAstVisitor::parseBlock(Block *block, bool hasNext, bool allowBraceless) -{ - bool hasOneLine = - (block->statements != nullptr && block->statements->next == nullptr) && allowBraceless; - - QString result = hasOneLine ? "\n" : "{\n"; - m_indentLevel++; - result += parseStatementList(block->statements); - m_indentLevel--; - - if (hasNext) - result += formatLine(hasOneLine ? "" : "} ", false); - - if (!hasNext && !hasOneLine) - result += formatLine("}", false); - - if (block->statements) { - m_blockNeededBraces |= !needsSemicolon(block->statements->statement->kind) - || (block->statements->next != nullptr); - } else { - m_blockNeededBraces = true; - } - - return result; -} - -static bool endsWithSemicolon(const QStringView s) -{ - return s.trimmed().endsWith(';'); -} - -QString DumpAstVisitor::parseStatement(Statement *statement, bool blockHasNext, - bool blockAllowBraceless) -{ - if (statement == nullptr) - return ""; - - switch (statement->kind) - { - case Node::Kind_EmptyStatement: - return ""; - case Node::Kind_ExpressionStatement: - return parseExpression(cast<ExpressionStatement *>(statement)->expression); - case Node::Kind_VariableStatement: - return parseVariableDeclarationList(cast<VariableStatement *>(statement)->declarations); - case Node::Kind_ReturnStatement: - return "return "+parseExpression(cast<ReturnStatement *>(statement)->expression); - case Node::Kind_ContinueStatement: - return "continue"; - case Node::Kind_BreakStatement: - return "break"; - case Node::Kind_SwitchStatement: { - auto *switchStatement = cast<SwitchStatement *>(statement); - - QString result = "switch ("+parseExpression(switchStatement->expression)+") "; - - result += parseCaseBlock(switchStatement->block); - - return result; - } - case Node::Kind_IfStatement: { - auto *ifStatement = cast<IfStatement *>(statement); - - m_blockNeededBraces = !blockAllowBraceless; - - QString ifFalse = parseStatement(ifStatement->ko, false, true); - QString ifTrue = parseStatement(ifStatement->ok, !ifFalse.isEmpty(), true); - - bool ifTrueBlock = ifStatement->ok->kind == Node::Kind_Block; - bool ifFalseBlock = ifStatement->ko - ? (ifStatement->ko->kind == Node::Kind_Block || ifStatement->ko->kind == Node::Kind_IfStatement) - : false; - - if (m_blockNeededBraces) { - ifFalse = parseStatement(ifStatement->ko, false, false); - ifTrue = parseStatement(ifStatement->ok, !ifFalse.isEmpty(), false); - } - - if (ifStatement->ok->kind != Node::Kind_Block && !endsWithSemicolon(ifTrue)) - ifTrue += ";"; - - if (ifStatement->ko && ifStatement->ko->kind != Node::Kind_Block - && ifStatement->ko->kind != Node::Kind_IfStatement && !endsWithSemicolon(ifFalse)) - ifFalse += ";"; - - QString result = "if (" + parseExpression(ifStatement->expression) + ")"; - - if (m_blockNeededBraces) { - if (ifStatement->ok->kind != Node::Kind_Block) { - QString result = "{\n"; - m_indentLevel++; - result += formatLine(ifTrue); - m_indentLevel--; - result += formatLine("} ", false); - ifTrue = result; - ifTrueBlock = true; - } - - if (ifStatement->ko && ifStatement->ko->kind != Node::Kind_Block && ifStatement->ko->kind != Node::Kind_IfStatement) { - QString result = "{\n"; - m_indentLevel++; - result += formatLine(ifFalse); - m_indentLevel--; - result += formatLine("} ", false); - ifFalse = result; - ifFalseBlock = true; - } - } - - if (ifTrueBlock) { - result += " " + ifTrue; - } else { - result += "\n"; - m_indentLevel++; - result += formatPartlyFormatedLines(ifTrue); - m_indentLevel--; - } - - if (!ifFalse.isEmpty()) - { - if (ifTrueBlock) - result += "else"; - else - result += formatLine("else", false); - - if (ifFalseBlock) { - // Blocks generate an extra newline that we don't want here. - if (!m_blockNeededBraces && ifFalse.endsWith(QLatin1String("\n"))) - ifFalse.chop(1); - - result += " " + ifFalse; - } else { - result += "\n"; - m_indentLevel++; - result += formatPartlyFormatedLines(ifFalse, false); - m_indentLevel--; - } - } - - return result; - } - case Node::Kind_ForStatement: { - auto *forStatement = cast<ForStatement *>(statement); - - QString expr = parseExpression(forStatement->expression); - QString result = "for ("; - - result += parseVariableDeclarationList(forStatement->declarations); - - result += "; "; - - result += parseExpression(forStatement->condition) + "; "; - result += parseExpression(forStatement->expression)+")"; - - const QString statement = parseStatement(forStatement->statement); - - if (!statement.isEmpty()) - result += " "+statement; - else - result += ";"; - - return result; - } - case Node::Kind_ForEachStatement: { - auto *forEachStatement = cast<ForEachStatement *>(statement); - - QString result = "for ("; - - PatternElement *patternElement = cast<PatternElement *>(forEachStatement->lhs); - - if (patternElement != nullptr) - result += parsePatternElement(patternElement); - else - result += parseExpression(forEachStatement->lhs->expressionCast()); - - switch (forEachStatement->type) - { - case ForEachType::In: - result += " in "; - break; - case ForEachType::Of: - result += " of "; - break; - } - - result += parseExpression(forEachStatement->expression) + ")"; - - const QString statement = parseStatement(forEachStatement->statement); - - if (!statement.isEmpty()) - result += " "+statement; - else - result += ";"; - - return result; - } - case Node::Kind_WhileStatement: { - auto *whileStatement = cast<WhileStatement *>(statement); - - m_blockNeededBraces = false; - - auto statement = parseStatement(whileStatement->statement, false, true); - - QString result = "while ("+parseExpression(whileStatement->expression) + ")"; - - if (!statement.isEmpty()) - result += (m_blockNeededBraces ? " " : "") + statement; - else - result += ";"; - - return result; - } - case Node::Kind_DoWhileStatement: { - auto *doWhileStatement = cast<DoWhileStatement *>(statement); - return "do " + parseBlock(cast<Block *>(doWhileStatement->statement), true, false) - + "while (" + parseExpression(doWhileStatement->expression) + ")"; - } - case Node::Kind_TryStatement: { - auto *tryStatement = cast<TryStatement *>(statement); - - Catch *catchExpr = tryStatement->catchExpression; - Finally *finallyExpr = tryStatement->finallyExpression; - - QString result; - - result += "try " + parseBlock(cast<Block *>(tryStatement->statement), true, false); - - result += "catch (" + parsePatternElement(catchExpr->patternElement, false) + ") " - + parseBlock(cast<Block *>(catchExpr->statement), finallyExpr, false); - - if (finallyExpr) { - result += "finally " + parseBlock(cast<Block *>(tryStatement->statement), false, false); - } - - return result; - } - case Node::Kind_Block: { - return parseBlock(cast<Block *>(statement), blockHasNext, blockAllowBraceless); - } - case Node::Kind_ThrowStatement: - return "throw "+parseExpression(cast<ThrowStatement *>(statement)->expression); - case Node::Kind_LabelledStatement: { - auto *labelledStatement = cast<LabelledStatement *>(statement); - QString result = labelledStatement->label+":\n"; - result += formatLine(parseStatement(labelledStatement->statement), false); - - return result; - } - case Node::Kind_WithStatement: { - auto *withStatement = cast<WithStatement *>(statement); - return "with (" + parseExpression(withStatement->expression) + ") " - + parseStatement(withStatement->statement); - } - case Node::Kind_DebuggerStatement: { - return "debugger"; - } - case Node::Kind_ExportDeclaration: - m_error = true; - return "export_decl_unsupported"; - case Node::Kind_ImportDeclaration: - m_error = true; - return "import_decl_unsupported"; - default: - m_error = true; - return "unknown_statement_"+QString::number(statement->kind); - } -} - -QString DumpAstVisitor::parseStatementList(StatementList *list) -{ - QString result = ""; - - if (list == nullptr) - return ""; - - result += getOrphanedComments(list); - - for (auto *item = list; item != nullptr; item = item->next) { - QString statement = parseStatement(item->statement->statementCast(), false, true); - if (statement.isEmpty()) - continue; - - QString commentFront = getComment(item->statement, Comment::Location::Front); - QString commentBackInline = getComment(item->statement, Comment::Location::Back_Inline); - - if (!commentFront.isEmpty()) - result += formatLine(commentFront); - - result += formatLine(statement + (needsSemicolon(item->statement->kind) ? ";" : "") - + commentBackInline); - } - - return result; -} - -bool DumpAstVisitor::visit(UiPublicMember *node) { - - QString commentFront = getComment(node, Comment::Location::Front); - QString commentBackInline = getComment(node, Comment::Location::Back_Inline); - - switch (node->type) - { - case UiPublicMember::Signal: - if (scope().m_firstSignal) { - if (scope().m_firstOfAll) - scope().m_firstOfAll = false; - else - addNewLine(); - - scope().m_firstSignal = false; - } - - addLine(commentFront); - addLine("signal "+node->name.toString()+"("+parseUiParameterList(node->parameters) + ")" - + commentBackInline); - break; - case UiPublicMember::Property: { - if (scope().m_firstProperty) { - if (scope().m_firstOfAll) - scope().m_firstOfAll = false; - else - addNewLine(); - - scope().m_firstProperty = false; - } - - const bool is_required = node->requiredToken.isValid(); - const bool is_default = node->defaultToken.isValid(); - const bool is_readonly = node->readonlyToken.isValid(); - const bool has_type_modifier = node->typeModifierToken.isValid(); - - QString prefix = ""; - QString statement = parseStatement(node->statement); - - if (!statement.isEmpty()) - statement.prepend(": "); - - if (is_required) - prefix += "required "; - - if (is_default) - prefix += "default "; - - if (is_readonly) - prefix += "readonly "; - - QString member_type = parseUiQualifiedId(node->memberType); - - if (has_type_modifier) - member_type = node->typeModifier + "<" + member_type + ">"; - - addLine(commentFront); - if (is_readonly && statement.isEmpty() - && scope().m_bindings.contains(node->name.toString())) { - m_result += formatLine(prefix + "property " + member_type + " ", false); - - scope().m_pendingBinding = true; - } else { - addLine(prefix + "property " + member_type + " " - + node->name+statement + commentBackInline); - } - break; - } - } - - return true; -} - -QString DumpAstVisitor::generateIndent(int indentLevel) const -{ - return QString(m_indentWidth * indentLevel, m_indentation == Indentation::Tabs ? '\t' : ' '); -} - -QString DumpAstVisitor::formatLine(QString line, bool newline) const -{ - QString result = generateIndent(m_indentLevel) + line; - if (newline) - result += "\n"; - - return result; -} - -QString DumpAstVisitor::formatPartlyFormatedLines(QString line, bool newline) const -{ - QString result; - - const auto lines = QStringView { line }.split('\n'); - auto it = lines.cbegin(); - const auto endi = lines.cend(); - if (it != endi) { - result += generateIndent(m_indentLevel) + *it; - ++it; - const QString addonIdent = generateIndent(1); - for (; it != endi; ++it) { - result += '\n'; - result += addonIdent + *it; - } - } - - if (newline) - result += "\n"; - - return result; -} - -void DumpAstVisitor::addNewLine(bool always) { - if (!always && m_result.endsWith("\n\n")) - return; - - m_result += "\n"; -} - -void DumpAstVisitor::addLine(QString line) { - // addLine does not support empty lines, use addNewLine(true) for that - if (line.isEmpty()) - return; - - m_result += formatLine(line); -} - -QHash<QString, UiObjectMember*> findBindings(UiObjectMemberList *list) { - QHash<QString, UiObjectMember*> bindings; - - // This relies on RestructureASTVisitor having run beforehand - - for (auto *item = list; item != nullptr; item = item->next) { - switch (item->member->kind) { - case Node::Kind_UiPublicMember: { - UiPublicMember *member = cast<UiPublicMember *>(item->member); - - if (member->type != UiPublicMember::Property) - continue; - - bindings[member->name.toString()] = nullptr; - - break; - } - case Node::Kind_UiObjectBinding: { - UiObjectBinding *binding = cast<UiObjectBinding *>(item->member); - - const QString name = parseUiQualifiedId(binding->qualifiedId); - - if (bindings.contains(name)) - bindings[name] = binding; - - break; - } - case Node::Kind_UiArrayBinding: { - UiArrayBinding *binding = cast<UiArrayBinding *>(item->member); - - const QString name = parseUiQualifiedId(binding->qualifiedId); - - if (bindings.contains(name)) - bindings[name] = binding; - - break; - } - case Node::Kind_UiScriptBinding: - // We can ignore UiScriptBindings since those are actually properly attached to the property - break; - } - } - - return bindings; -} - -bool DumpAstVisitor::visit(UiInlineComponent *node) -{ - m_component_name = node->name.toString(); - return true; -} - -bool DumpAstVisitor::visit(UiObjectDefinition *node) { - if (scope().m_firstObject) { - if (scope().m_firstOfAll) - scope().m_firstOfAll = false; - else - addNewLine(); - - scope().m_firstObject = false; - } - - addLine(getComment(node, Comment::Location::Front)); - addLine(getComment(node, Comment::Location::Front_Inline)); - - QString component = ""; - - if (!m_component_name.isEmpty()) { - component = "component "+m_component_name+": "; - m_component_name = ""; - } - - addLine(component + parseUiQualifiedId(node->qualifiedTypeNameId) + " {"); - - m_indentLevel++; - - ScopeProperties props; - props.m_bindings = findBindings(node->initializer->members); - m_scope_properties.push(props); - - m_result += getOrphanedComments(node); - - return true; -} - -void DumpAstVisitor::endVisit(UiObjectDefinition *node) { - m_indentLevel--; - - m_scope_properties.pop(); - - bool need_comma = scope().m_inArrayBinding && scope().m_lastInArrayBinding != node; - - addLine(need_comma ? "}," : "}"); - addLine(getComment(node, Comment::Location::Back)); - if (!scope().m_inArrayBinding) - addNewLine(); -} - -bool DumpAstVisitor::visit(UiEnumDeclaration *node) { - - addNewLine(); - - addLine(getComment(node, Comment::Location::Front)); - addLine("enum " + node->name + " {"); - m_indentLevel++; - m_result += getOrphanedComments(node); - - return true; -} - -void DumpAstVisitor::endVisit(UiEnumDeclaration *) { - m_indentLevel--; - addLine("}"); - - addNewLine(); -} - -bool DumpAstVisitor::visit(UiEnumMemberList *node) { - for (auto *members = node; members != nullptr; members = members->next) { - - addLine(getListItemComment(members->memberToken, Comment::Location::Front)); - - QString line = members->member.toString(); - - if (members->valueToken.isValid()) - line += " = "+QString::number(members->value); - - if (members->next != nullptr) - line += ","; - - line += getListItemComment(members->memberToken, Comment::Location::Back_Inline); - - addLine(line); - } - - return true; -} - -bool DumpAstVisitor::visit(UiScriptBinding *node) { - if (scope().m_firstBinding) { - if (scope().m_firstOfAll) - scope().m_firstOfAll = false; - else - addNewLine(); - - if (parseUiQualifiedId(node->qualifiedId) != "id") - scope().m_firstBinding = false; - } - - addLine(getComment(node, Comment::Location::Front)); - - bool multiline = !needsSemicolon(node->statement->kind); - - if (multiline) { - m_indentLevel++; - } - - QString statement = parseStatement(node->statement); - - if (multiline) { - statement = "{\n" + formatLine(statement); - m_indentLevel--; - statement += formatLine("}", false); - } - - QString result = parseUiQualifiedId(node->qualifiedId) + ":"; - - if (!statement.isEmpty()) - result += " "+statement; - else - result += ";"; - - result += getComment(node, Comment::Location::Back_Inline); - - addLine(result); - - return true; -} - -bool DumpAstVisitor::visit(UiArrayBinding *node) { - if (!scope().m_pendingBinding && scope().m_firstBinding) { - if (scope().m_firstOfAll) - scope().m_firstOfAll = false; - else - addNewLine(); - - scope().m_firstBinding = false; - } - - if (scope().m_pendingBinding) { - m_result += parseUiQualifiedId(node->qualifiedId)+ ": [\n"; - scope().m_pendingBinding = false; - } else { - addLine(getComment(node, Comment::Location::Front)); - addLine(parseUiQualifiedId(node->qualifiedId)+ ": ["); - } - - m_indentLevel++; - - ScopeProperties props; - props.m_inArrayBinding = true; - - for (auto *item = node->members; item != nullptr; item = item->next) { - if (item->next == nullptr) - props.m_lastInArrayBinding = item->member; - } - - m_scope_properties.push(props); - - m_result += getOrphanedComments(node); - - return true; -} - -void DumpAstVisitor::endVisit(UiArrayBinding *) { - m_indentLevel--; - m_scope_properties.pop(); - addLine("]"); -} - -bool DumpAstVisitor::visit(FunctionDeclaration *node) { - if (scope().m_firstFunction) { - if (scope().m_firstOfAll) - scope().m_firstOfAll = false; - else - addNewLine(); - - scope().m_firstFunction = false; - } - - addLine(getComment(node, Comment::Location::Front)); - - QString head = "function"; - - if (node->isGenerator) - head += "*"; - - head += " "+node->name+"("+parseFormalParameterList(node->formals)+")"; - - if (node->typeAnnotation != nullptr) - head += " : " + parseType(node->typeAnnotation->type); - - head += " {"; - - addLine(head); - m_indentLevel++; - - return true; -} - -void DumpAstVisitor::endVisit(FunctionDeclaration *node) -{ - m_result += parseStatementList(node->body); - m_indentLevel--; - addLine("}"); - addNewLine(); -} - -bool DumpAstVisitor::visit(UiObjectBinding *node) { - if (!scope().m_pendingBinding && scope().m_firstObject) { - if (scope().m_firstOfAll) - scope().m_firstOfAll = false; - else - addNewLine(); - - scope().m_firstObject = false; - } - - QString name = parseUiQualifiedId(node->qualifiedTypeNameId); - - QString result = name; - - ScopeProperties props; - props.m_bindings = findBindings(node->initializer->members); - m_scope_properties.push(props); - - if (node->hasOnToken) - result += " on "+parseUiQualifiedId(node->qualifiedId); - else - result.prepend(parseUiQualifiedId(node->qualifiedId) + ": "); - - if (scope().m_pendingBinding) { - m_result += result + " {\n"; - - scope().m_pendingBinding = false; - } else { - addNewLine(); - addLine(getComment(node, Comment::Location::Front)); - addLine(getComment(node, Comment::Location::Front_Inline)); - addLine(result + " {"); - } - - m_indentLevel++; - - return true; -} - -void DumpAstVisitor::endVisit(UiObjectBinding *node) { - m_indentLevel--; - m_scope_properties.pop(); - - addLine("}"); - addLine(getComment(node, Comment::Location::Back)); - - addNewLine(); -} - -bool DumpAstVisitor::visit(UiImport *node) { - scope().m_firstOfAll = false; - - addLine(getComment(node, Comment::Location::Front)); - - QString result = "import "; - - if (!node->fileName.isEmpty()) - result += escapeString(node->fileName.toString()); - else - result += parseUiQualifiedId(node->importUri); - - if (node->version) { - const auto version = node->version->version; - - if (version.hasMajorVersion()) { - result += " " + QString::number(version.majorVersion()); - - if (version.hasMinorVersion()) - result += "." + QString::number(version.minorVersion()); - } - } - - if (node->asToken.isValid()) { - result +=" as " + node->importId; - } - - result += getComment(node, Comment::Location::Back_Inline); - - addLine(result); - - return true; -} - -bool DumpAstVisitor::visit(UiPragma *node) { - scope().m_firstOfAll = false; - - addLine(getComment(node, Comment::Location::Front)); - QString result = "pragma "+ node->name; - result += getComment(node, Comment::Location::Back_Inline); - - addLine(result); - - return true; -} - -bool DumpAstVisitor::visit(UiAnnotation *node) -{ - if (scope().m_firstObject) { - if (scope().m_firstOfAll) - scope().m_firstOfAll = false; - else - addNewLine(); - - scope().m_firstObject = false; - } - - addLine(getComment(node, Comment::Location::Front)); - addLine(QLatin1String("@") + parseUiQualifiedId(node->qualifiedTypeNameId) + " {"); - - m_indentLevel++; - - ScopeProperties props; - props.m_bindings = findBindings(node->initializer->members); - m_scope_properties.push(props); - - m_result += getOrphanedComments(node); - - return true; -} - -void DumpAstVisitor::endVisit(UiAnnotation *node) { - m_indentLevel--; - - m_scope_properties.pop(); - - addLine("}"); - addLine(getComment(node, Comment::Location::Back)); -} diff --git a/tools/qmlformat/dumpastvisitor.h b/tools/qmlformat/dumpastvisitor.h deleted file mode 100644 index 657592f403..0000000000 --- a/tools/qmlformat/dumpastvisitor.h +++ /dev/null @@ -1,161 +0,0 @@ -/**************************************************************************** -** -** 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 DUMPAST_H -#define DUMPAST_H - -#include <QtQml/private/qqmljsastvisitor_p.h> -#include <QtQml/private/qqmljsast_p.h> - -#include <QHash> -#include <QStack> - -#include "commentastvisitor.h" - -using namespace QQmlJS::AST; -using namespace QQmlJS; - -class DumpAstVisitor : protected Visitor -{ -public: - enum Indentation { Tabs, Spaces }; - - DumpAstVisitor(QQmlJS::Engine *engine, Node *rootNode, CommentAstVisitor *comment, - int indentWidth, Indentation indentation); - - QString toString() const { return m_result; } - - bool preVisit(Node *) override; - - bool visit(UiScriptBinding *node) override; - - bool visit(UiArrayBinding *node) override; - void endVisit(UiArrayBinding *node) override; - - bool visit(UiObjectBinding *node) override; - void endVisit(UiObjectBinding *node) override; - - bool visit(FunctionDeclaration *node) override; - void endVisit(FunctionDeclaration *node) override; - - bool visit(UiInlineComponent *node) override; - - bool visit(UiObjectDefinition *node) override; - void endVisit(UiObjectDefinition *node) override; - - bool visit(UiEnumDeclaration *node) override; - void endVisit(UiEnumDeclaration *node) override; - - bool visit(UiEnumMemberList *node) override; - bool visit(UiPublicMember *node) override; - bool visit(UiImport *node) override; - bool visit(UiPragma *node) override; - - bool visit(UiAnnotation *node) override; - void endVisit(UiAnnotation *node) override; - - void throwRecursionDepthError() override {} - - bool error() const { return m_error; } -private: - struct ScopeProperties { - bool m_firstOfAll = true; - bool m_firstSignal = true; - bool m_firstProperty = true; - bool m_firstBinding = true; - bool m_firstObject = true; - bool m_firstFunction = true; - bool m_inArrayBinding = false; - bool m_pendingBinding = false; - - UiObjectMember* m_lastInArrayBinding = nullptr; - QHash<QString, UiObjectMember*> m_bindings; - }; - - QString generateIndent(int indentLevel) const; - QString formatLine(QString line, bool newline = true) const; - QString formatPartlyFormatedLines(QString line, bool newline = true) const; - - QString formatComment(const Comment &comment) const; - - QString getComment(Node *node, Comment::Location location) const; - QString getListItemComment(SourceLocation srcLocation, Comment::Location location) const; - - void addNewLine(bool always = false); - void addLine(QString line); - - QString getOrphanedComments(Node *node) const; - - QString parseStatement(Statement *statement, bool blockHasNext = false, - bool blockAllowBraceless = false); - QString parseStatementList(StatementList *list); - - QString parseExpression(ExpressionNode *expression); - - QString parsePatternElement(PatternElement *element, bool scope = true); - QString parsePatternElementList(PatternElementList *element); - - QString parsePatternProperty(PatternProperty *property); - QString parsePatternPropertyList(PatternPropertyList *list); - - QString parseArgumentList(ArgumentList *list); - - QString parseUiParameterList(UiParameterList *list); - - QString parseVariableDeclarationList(VariableDeclarationList *list); - - QString parseCaseBlock(CaseBlock *block); - QString parseBlock(Block *block, bool hasNext, bool allowBraceless); - - QString parseExportsList(ExportsList *list); - QString parseExportSpecifier(ExportSpecifier *specifier); - - QString parseFormalParameterList(FormalParameterList *list); - - QString parseType(Type *type); - - QString parseFunctionExpression(FunctionExpression *expression, bool omitFunction = false); - - ScopeProperties& scope() { return m_scope_properties.top(); } - - int m_indentLevel = 0; - - bool m_error = false; - bool m_blockNeededBraces = false; - - QStack<ScopeProperties> m_scope_properties; - - QString m_result = ""; - QString m_component_name = ""; - QQmlJS::Engine *m_engine; - CommentAstVisitor *m_comment; - int m_indentWidth; - Indentation m_indentation; -}; - -#endif // DUMPAST_H diff --git a/tools/qmlformat/main.cpp b/tools/qmlformat/main.cpp deleted file mode 100644 index da3c1772fb..0000000000 --- a/tools/qmlformat/main.cpp +++ /dev/null @@ -1,293 +0,0 @@ -/**************************************************************************** -** -** 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 <QCoreApplication> -#include <QFile> -#include <QTextStream> - -#include <QtQml/private/qqmljslexer_p.h> -#include <QtQml/private/qqmljsparser_p.h> -#include <QtQml/private/qqmljsengine_p.h> -#include <QtQml/private/qqmljsastvisitor_p.h> -#include <QtQml/private/qqmljsast_p.h> - -#if QT_CONFIG(commandlineparser) -#include <QCommandLineParser> -#endif - -#include "commentastvisitor.h" -#include "dumpastvisitor.h" -#include "restructureastvisitor.h" - -struct Options -{ - bool verbose = false; - bool inplace = false; - bool force = false; - bool tabs = false; - bool valid = false; - - int indentWidth = 4; - bool indentWidthSet = false; - QString newline = "native"; - - QStringList files; - QStringList arguments; - QStringList errors; -}; - -bool parseFile(const QString &filename, const Options &options) -{ - QFile file(filename); - - if (!file.open(QIODevice::Text | QIODevice::ReadOnly)) { - qWarning().noquote() << "Failed to open" << filename << "for reading."; - return false; - } - - QString code = QString::fromUtf8(file.readAll()); - file.close(); - - QQmlJS::Engine engine; - QQmlJS::Lexer lexer(&engine); - - lexer.setCode(code, 1, true); - QQmlJS::Parser parser(&engine); - - bool success = parser.parse(); - - if (!success) { - const auto diagnosticMessages = parser.diagnosticMessages(); - for (const QQmlJS::DiagnosticMessage &m : diagnosticMessages) { - qWarning().noquote() << QString::fromLatin1("%1:%2 : %3") - .arg(filename).arg(m.loc.startLine).arg(m.message); - } - - qWarning().noquote() << "Failed to parse" << filename; - return false; - } - - // Try to attach comments to AST nodes - CommentAstVisitor comment(&engine, parser.rootNode()); - - if (options.verbose) - qWarning().noquote() << comment.attachedComments().size() << "comment(s) attached."; - - if (options.verbose) { - int orphaned = 0; - - for (const auto& orphanList : comment.orphanComments().values()) - orphaned += orphanList.size(); - - qWarning().noquote() << orphaned << "comments are orphans."; - } - - // Do the actual restructuring - RestructureAstVisitor restructure(parser.rootNode()); - - // Turn AST back into source code - if (options.verbose) - qWarning().noquote() << "Dumping" << filename; - - DumpAstVisitor dump( - &engine, parser.rootNode(), &comment, options.tabs ? 1 : options.indentWidth, - options.tabs ? DumpAstVisitor::Indentation::Tabs : DumpAstVisitor::Indentation::Spaces); - - QString dumpCode = dump.toString(); - - lexer.setCode(dumpCode, 1, true); - - bool dumpSuccess = parser.parse(); - - if (!dumpSuccess) { - if (options.verbose) { - const auto diagnosticMessages = parser.diagnosticMessages(); - for (const QQmlJS::DiagnosticMessage &m : diagnosticMessages) { - qWarning().noquote() << QString::fromLatin1("<formatted>:%2 : %3") - .arg(m.loc.startLine).arg(m.message); - } - } - - qWarning().noquote() << "Failed to parse formatted code."; - } - - if (dump.error() || !dumpSuccess) { - if (options.force) { - qWarning().noquote() << "An error has occurred. The output may not be reliable."; - } else { - qWarning().noquote() << "An error has occurred. Aborting."; - return false; - } - } - - const bool native = options.newline == "native"; - - if (!native) { - if (options.newline == "macos") { - dumpCode = dumpCode.replace("\n", "\r"); - } else if (options.newline == "windows") { - dumpCode = dumpCode.replace("\n", "\r\n"); - } else if (options.newline == "unix") { - // Nothing needs to be done for unix line-endings - } else { - qWarning().noquote() << "Unknown line ending type" << options.newline; - return false; - } - } - - if (options.inplace) { - if (options.verbose) - qWarning().noquote() << "Writing to file" << filename; - - if (!file.open(native ? QIODevice::WriteOnly | QIODevice::Text : QIODevice::WriteOnly)) { - qWarning().noquote() << "Failed to open" << filename << "for writing"; - return false; - } - - file.write(dumpCode.toUtf8()); - file.close(); - } else { - QFile out; - out.open(stdout, QIODevice::WriteOnly); - out.write(dumpCode.toUtf8()); - } - - return true; -} - -Options buildCommandLineOptions(const QCoreApplication &app) -{ -#if QT_CONFIG(commandlineparser) - QCommandLineParser parser; - parser.setApplicationDescription("Formats QML files according to the QML Coding Conventions."); - parser.addHelpOption(); - parser.addVersionOption(); - - parser.addOption(QCommandLineOption({"V", "verbose"}, - QStringLiteral("Verbose mode. Outputs more detailed information."))); - - parser.addOption(QCommandLineOption({"i", "inplace"}, - QStringLiteral("Edit file in-place instead of outputting to stdout."))); - - parser.addOption(QCommandLineOption({"f", "force"}, - QStringLiteral("Continue even if an error has occurred."))); - - parser.addOption( - QCommandLineOption({ "t", "tabs" }, QStringLiteral("Use tabs instead of spaces."))); - - parser.addOption(QCommandLineOption({ "w", "indent-width" }, - QStringLiteral("How many spaces are used when indenting."), - "width", "4")); - - parser.addOption(QCommandLineOption( - { "F", "files" }, QStringLiteral("Format all files listed in file, in-place"), "file")); - - parser.addOption(QCommandLineOption({"l", "newline"}, - QStringLiteral("Override the new line format to use (native macos unix windows)."), - "newline", "native")); - - parser.addPositionalArgument("filenames", "files to be processed by qmlformat"); - - parser.process(app); - - bool indentWidthOkay = false; - const int indentWidth = parser.value("indent-width").toInt(&indentWidthOkay); - if (!indentWidthOkay) { - Options options; - options.errors.push_back("Error: Invalid value passed to -w"); - return options; - } - - QStringList files; - QFile file(parser.value("files")); - file.open(QIODevice::Text | QIODevice::ReadOnly); - if (file.isOpen()) { - QTextStream in(&file); - while (!in.atEnd()) { - QString file = in.readLine(); - - if (file.isEmpty()) - continue; - - files.push_back(file); - } - } - - Options options; - options.verbose = parser.isSet("verbose"); - options.inplace = parser.isSet("inplace"); - options.force = parser.isSet("force"); - options.tabs = parser.isSet("tabs"); - options.valid = true; - - options.indentWidth = indentWidth; - options.indentWidthSet = parser.isSet("indent-width"); - options.newline = parser.value("newline"); - options.files = files; - options.arguments = parser.positionalArguments(); - return options; -#else - return Options {}; -#endif -} - -int main(int argc, char *argv[]) -{ - QCoreApplication app(argc, argv); - QCoreApplication::setApplicationName("qmlformat"); - QCoreApplication::setApplicationVersion("1.0"); - - const auto options = buildCommandLineOptions(app); - if (!options.valid) { - for (const auto &error : options.errors) { - qWarning().noquote() << error; - } - - return -1; - } - - bool success = true; - if (!options.files.isEmpty()) { - if (!options.arguments.isEmpty()) - qWarning() << "Warning: Positional arguments are ignored when -F is used"; - - for (const QString &file : options.files) { - Q_ASSERT(!file.isEmpty()); - - if (!parseFile(file, options)) - success = false; - } - } else { - for (const QString &file : options.arguments) { - if (!parseFile(file, options)) - success = false; - } - } - - return success ? 0 : 1; -} diff --git a/tools/qmlformat/qmlformat.cpp b/tools/qmlformat/qmlformat.cpp new file mode 100644 index 0000000000..e26a6412c9 --- /dev/null +++ b/tools/qmlformat/qmlformat.cpp @@ -0,0 +1,390 @@ +// Copyright (C) 2019 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include <QCoreApplication> +#include <QFile> +#include <QTextStream> + +#include <QtQml/private/qqmljslexer_p.h> +#include <QtQml/private/qqmljsparser_p.h> +#include <QtQml/private/qqmljsengine_p.h> +#include <QtQml/private/qqmljsastvisitor_p.h> +#include <QtQml/private/qqmljsast_p.h> +#include <QtQmlDom/private/qqmldomitem_p.h> +#include <QtQmlDom/private/qqmldomexternalitems_p.h> +#include <QtQmlDom/private/qqmldomtop_p.h> +#include <QtQmlDom/private/qqmldomoutwriter_p.h> + +#if QT_CONFIG(commandlineparser) +# include <QCommandLineParser> +#endif + +#include <QtQmlToolingSettings/private/qqmltoolingsettings_p.h> + + +using namespace QQmlJS::Dom; + +struct Options +{ + bool verbose = false; + bool inplace = false; + bool force = false; + bool tabs = false; + bool valid = false; + bool normalize = false; + bool ignoreSettings = false; + bool writeDefaultSettings = false; + bool objectsSpacing = false; + bool functionsSpacing = false; + + int indentWidth = 4; + bool indentWidthSet = false; + QString newline = "native"; + + QStringList files; + QStringList arguments; + QStringList errors; +}; + +// TODO refactor +// Move out to the LineWriterOptions class / helper +static LineWriterOptions composeLwOptions(const Options &options, QStringView code) +{ + LineWriterOptions lwOptions; + lwOptions.formatOptions.indentSize = options.indentWidth; + lwOptions.formatOptions.useTabs = options.tabs; + lwOptions.updateOptions = LineWriterOptions::Update::None; + if (options.newline == "native") { + // find out current line endings... + int newlineIndex = code.indexOf(QChar(u'\n')); + int crIndex = code.indexOf(QChar(u'\r')); + if (newlineIndex >= 0) { + if (crIndex >= 0) { + if (crIndex + 1 == newlineIndex) + lwOptions.lineEndings = LineWriterOptions::LineEndings::Windows; + else + qWarning().noquote() << "Invalid line ending in file, using default"; + + } else { + lwOptions.lineEndings = LineWriterOptions::LineEndings::Unix; + } + } else if (crIndex >= 0) { + lwOptions.lineEndings = LineWriterOptions::LineEndings::OldMacOs; + } else { + qWarning().noquote() << "Unknown line ending in file, using default"; + } + } else if (options.newline == "macos") { + lwOptions.lineEndings = LineWriterOptions::LineEndings::OldMacOs; + } else if (options.newline == "windows") { + lwOptions.lineEndings = LineWriterOptions::LineEndings::Windows; + } else if (options.newline == "unix") { + lwOptions.lineEndings = LineWriterOptions::LineEndings::Unix; + } else { + qWarning().noquote() << "Unknown line ending type" << options.newline << ", using default"; + } + + if (options.normalize) + lwOptions.attributesSequence = LineWriterOptions::AttributesSequence::Normalize; + else + lwOptions.attributesSequence = LineWriterOptions::AttributesSequence::Preserve; + + lwOptions.objectsSpacing = options.objectsSpacing; + lwOptions.functionsSpacing = options.functionsSpacing; + return lwOptions; +} + +static void logParsingErrors(const DomItem &fileItem, const QString &filename) +{ + fileItem.iterateErrors( + [](const DomItem &, const ErrorMessage &msg) { + errorToQDebug(msg); + return true; + }, + true); + qWarning().noquote() << "Failed to parse" << filename; +} + +// TODO +// refactor this workaround. ExternalOWningItem is not recognized as an owning type +// in ownerAs. +static std::shared_ptr<ExternalOwningItem> getFileItemOwner(const DomItem &fileItem) +{ + std::shared_ptr<ExternalOwningItem> filePtr = nullptr; + switch (fileItem.internalKind()) { + case DomType::JsFile: + filePtr = fileItem.ownerAs<JsFile>(); + break; + default: + filePtr = fileItem.ownerAs<QmlFile>(); + break; + } + return filePtr; +} + +// TODO refactor +// Introduce better encapsulation and separation of concerns and move to DOM API +// returns a DomItem corresponding to the loaded file and bool indicating the validity of the file +static std::pair<DomItem, bool> parse(const QString &filename) +{ + auto envPtr = + DomEnvironment::create(QStringList(), + QQmlJS::Dom::DomEnvironment::Option::SingleThreaded + | QQmlJS::Dom::DomEnvironment::Option::NoDependencies); + // placeholder for a node + // containing metadata (ExternalItemInfo) about the loaded file + DomItem fMetadataItem; + envPtr->loadFile(FileToLoad::fromFileSystem(envPtr, filename), + // callback called when everything is loaded that receives the + // loaded external file pair (path, oldValue, newValue) + [&fMetadataItem](Path, const DomItem &, const DomItem &extItemInfo) { + fMetadataItem = extItemInfo; + }); + auto fItem = fMetadataItem.fileObject(); + auto filePtr = getFileItemOwner(fItem); + return { fItem, filePtr && filePtr->isValid() }; +} + +static bool parseFile(const QString &filename, const Options &options) +{ + const auto [fileItem, validFile] = parse(filename); + if (!validFile) { + logParsingErrors(fileItem, filename); + return false; + } + + // Turn AST back into source code + if (options.verbose) + qWarning().noquote() << "Dumping" << filename; + + const auto &code = getFileItemOwner(fileItem)->code(); + auto lwOptions = composeLwOptions(options, code); + WriteOutChecks checks = WriteOutCheck::Default; + //Disable writeOutChecks for some usecases + if (options.force || + code.size() > 32000 || + fileItem.internalKind() == DomType::JsFile) { + checks = WriteOutCheck::None; + } + + bool res = false; + if (options.inplace) { + if (options.verbose) + qWarning().noquote() << "Writing to file" << filename; + FileWriter fw; + const unsigned numberOfBackupFiles = 0; + res = fileItem.writeOut(filename, numberOfBackupFiles, lwOptions, &fw, checks); + } else { + QFile out; + if (out.open(stdout, QIODevice::WriteOnly)) { + LineWriter lw([&out](QStringView s) { out.write(s.toUtf8()); }, filename, lwOptions); + OutWriter ow(lw); + res = fileItem.writeOutForFile(ow, checks); + ow.flush(); + } else { + res = false; + } + } + return res; +} + +Options buildCommandLineOptions(const QCoreApplication &app) +{ +#if QT_CONFIG(commandlineparser) + QCommandLineParser parser; + parser.setApplicationDescription("Formats QML files according to the QML Coding Conventions."); + parser.addHelpOption(); + parser.addVersionOption(); + + parser.addOption( + QCommandLineOption({ "V", "verbose" }, + QStringLiteral("Verbose mode. Outputs more detailed information."))); + + QCommandLineOption writeDefaultsOption( + QStringList() << "write-defaults", + QLatin1String("Writes defaults settings to .qmlformat.ini and exits (Warning: This " + "will overwrite any existing settings and comments!)")); + parser.addOption(writeDefaultsOption); + + QCommandLineOption ignoreSettings(QStringList() << "ignore-settings", + QLatin1String("Ignores all settings files and only takes " + "command line options into consideration")); + parser.addOption(ignoreSettings); + + parser.addOption(QCommandLineOption( + { "i", "inplace" }, + QStringLiteral("Edit file in-place instead of outputting to stdout."))); + + parser.addOption(QCommandLineOption({ "f", "force" }, + QStringLiteral("Continue even if an error has occurred."))); + + parser.addOption( + QCommandLineOption({ "t", "tabs" }, QStringLiteral("Use tabs instead of spaces."))); + + parser.addOption(QCommandLineOption({ "w", "indent-width" }, + QStringLiteral("How many spaces are used when indenting."), + "width", "4")); + + parser.addOption(QCommandLineOption({ "n", "normalize" }, + QStringLiteral("Reorders the attributes of the objects " + "according to the QML Coding Guidelines."))); + + parser.addOption(QCommandLineOption( + { "F", "files" }, QStringLiteral("Format all files listed in file, in-place"), "file")); + + parser.addOption(QCommandLineOption( + { "l", "newline" }, + QStringLiteral("Override the new line format to use (native macos unix windows)."), + "newline", "native")); + + parser.addOption(QCommandLineOption(QStringList() << "objects-spacing", QStringLiteral("Ensure spaces between objects (only works with normalize option)."))); + + parser.addOption(QCommandLineOption(QStringList() << "functions-spacing", QStringLiteral("Ensure spaces between functions (only works with normalize option)."))); + + parser.addPositionalArgument("filenames", "files to be processed by qmlformat"); + + parser.process(app); + + if (parser.isSet(writeDefaultsOption)) { + Options options; + options.writeDefaultSettings = true; + options.valid = true; + return options; + } + + bool indentWidthOkay = false; + const int indentWidth = parser.value("indent-width").toInt(&indentWidthOkay); + if (!indentWidthOkay) { + Options options; + options.errors.push_back("Error: Invalid value passed to -w"); + return options; + } + + QStringList files; + if (!parser.value("files").isEmpty()) { + QFile file(parser.value("files")); + if (file.open(QIODevice::Text | QIODevice::ReadOnly)) { + QTextStream in(&file); + while (!in.atEnd()) { + QString file = in.readLine(); + + if (file.isEmpty()) + continue; + + files.push_back(file); + } + } + } + + Options options; + options.verbose = parser.isSet("verbose"); + options.inplace = parser.isSet("inplace"); + options.force = parser.isSet("force"); + options.tabs = parser.isSet("tabs"); + options.normalize = parser.isSet("normalize"); + options.ignoreSettings = parser.isSet("ignore-settings"); + options.objectsSpacing = parser.isSet("objects-spacing"); + options.functionsSpacing = parser.isSet("functions-spacing"); + options.valid = true; + + options.indentWidth = indentWidth; + options.indentWidthSet = parser.isSet("indent-width"); + options.newline = parser.value("newline"); + options.files = files; + options.arguments = parser.positionalArguments(); + return options; +#else + return Options {}; +#endif +} + +int main(int argc, char *argv[]) +{ + QCoreApplication app(argc, argv); + QCoreApplication::setApplicationName("qmlformat"); + QCoreApplication::setApplicationVersion(QT_VERSION_STR); + + QQmlToolingSettings settings(QLatin1String("qmlformat")); + + const QString &useTabsSetting = QStringLiteral("UseTabs"); + settings.addOption(useTabsSetting); + + const QString &indentWidthSetting = QStringLiteral("IndentWidth"); + settings.addOption(indentWidthSetting, 4); + + const QString &normalizeSetting = QStringLiteral("NormalizeOrder"); + settings.addOption(normalizeSetting); + + const QString &newlineSetting = QStringLiteral("NewlineType"); + settings.addOption(newlineSetting, QStringLiteral("native")); + + const QString &objectsSpacingSetting = QStringLiteral("ObjectsSpacing"); + settings.addOption(objectsSpacingSetting); + + const QString &functionsSpacingSetting = QStringLiteral("FunctionsSpacing"); + settings.addOption(functionsSpacingSetting); + + const auto options = buildCommandLineOptions(app); + if (!options.valid) { + for (const auto &error : options.errors) { + qWarning().noquote() << error; + } + + return -1; + } + + if (options.writeDefaultSettings) + return settings.writeDefaults() ? 0 : -1; + + auto getSettings = [&](const QString &file, Options options) { + // Perform formatting inplace if --files option is set. + if (!options.files.isEmpty()) + options.inplace = true; + + if (options.ignoreSettings || !settings.search(file)) + return options; + + Options perFileOptions = options; + + // Allow for tab settings to be overwritten by the command line + if (!options.indentWidthSet) { + if (settings.isSet(indentWidthSetting)) + perFileOptions.indentWidth = settings.value(indentWidthSetting).toInt(); + if (settings.isSet(useTabsSetting)) + perFileOptions.tabs = settings.value(useTabsSetting).toBool(); + } + + if (settings.isSet(normalizeSetting)) + perFileOptions.normalize = settings.value(normalizeSetting).toBool(); + + if (settings.isSet(newlineSetting)) + perFileOptions.newline = settings.value(newlineSetting).toString(); + + if (settings.isSet(objectsSpacingSetting)) + perFileOptions.objectsSpacing = settings.value(objectsSpacingSetting).toBool(); + + if (settings.isSet(functionsSpacingSetting)) + perFileOptions.functionsSpacing = settings.value(functionsSpacingSetting).toBool(); + + return perFileOptions; + }; + + bool success = true; + if (!options.files.isEmpty()) { + if (!options.arguments.isEmpty()) + qWarning() << "Warning: Positional arguments are ignored when -F is used"; + + for (const QString &file : options.files) { + Q_ASSERT(!file.isEmpty()); + + if (!parseFile(file, getSettings(file, options))) + success = false; + } + } else { + for (const QString &file : options.arguments) { + if (!parseFile(file, getSettings(file, options))) + success = false; + } + } + + return success ? 0 : 1; +} diff --git a/tools/qmlformat/restructureastvisitor.cpp b/tools/qmlformat/restructureastvisitor.cpp deleted file mode 100644 index 45957230d8..0000000000 --- a/tools/qmlformat/restructureastvisitor.cpp +++ /dev/null @@ -1,194 +0,0 @@ -/**************************************************************************** -** -** 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 "restructureastvisitor.h" - -#include <QList> - -RestructureAstVisitor::RestructureAstVisitor(Node *rootNode) -{ - rootNode->accept(this); -} - -template<typename T> -static QList<T *> findKind(UiObjectMemberList *list) -{ - QList<T *> members; - for (auto *item = list; item != nullptr; item = item->next) { - if (cast<T *>(item->member) != nullptr) - members.append(cast<T *>(item->member)); - } - - return members; -} - -template<typename T> -static QList<T *> findKind(UiHeaderItemList *list) -{ - QList<T *> members; - for (auto *item = list; item != nullptr; item = item->next) { - if (cast<T *>(item->headerItem) != nullptr) - members.append(cast<T *>(item->headerItem)); - } - - return members; -} - -static QString parseUiQualifiedId(UiQualifiedId *id) -{ - QString name = id->name.toString(); - for (auto *item = id->next; item != nullptr; item = item->next) { - name += "." + item->name; - } - - return name; -} - -void RestructureAstVisitor::endVisit(UiObjectMemberList *node) -{ - QList<UiObjectMember*> correctOrder; - - QList<UiScriptBinding*> largeScriptBinding; - - UiObjectMember *states = nullptr; - UiObjectMember *transitions = nullptr; - - auto enumDeclarations = findKind<UiEnumDeclaration>(node); - auto scriptBindings = findKind<UiScriptBinding>(node); - auto arrayBindings = findKind<UiArrayBinding>(node); - auto publicMembers = findKind<UiPublicMember>(node); - auto sourceElements = findKind<UiSourceElement>(node); - auto objectDefinitions = findKind<UiObjectDefinition>(node); - - // Look for transitions and states - for (auto *binding : findKind<UiObjectBinding>(node)) { - const QString name = parseUiQualifiedId(binding->qualifiedId); - - if (name == "transitions") - transitions = binding; - else if (name == "states") - states = binding; - } - - for (auto it = arrayBindings.begin(); it != arrayBindings.end();) { - const QString name = parseUiQualifiedId((*it)->qualifiedId); - - if (name == "transitions") { - transitions = *it; - it = arrayBindings.erase(it); - } else if (name == "states") { - states = *it; - it = arrayBindings.erase(it); - } else { - it++; - } - } - - // Find large script bindings - for (auto it = scriptBindings.begin(); it != scriptBindings.end();) { - // A binding is considered large if it uses a block - if ((*it)->statement->kind != Node::Kind_Block) { - it++; - continue; - } - - largeScriptBinding.push_back(*it); - it = scriptBindings.erase(it); - } - - // This structure is based on https://doc.qt.io/qt-5/qml-codingconventions.html - - // 1st id - for (auto *binding : scriptBindings) { - if (parseUiQualifiedId(binding->qualifiedId) == "id") { - correctOrder.append(binding); - - scriptBindings.removeOne(binding); - break; - } - } - - // 2nd enums - for (auto *enumDeclaration : enumDeclarations) - correctOrder.append(enumDeclaration); - - // 3rd property declarations - for (auto *publicMember : publicMembers) { - if (publicMember->type != UiPublicMember::Property) - continue; - - correctOrder.append(publicMember); - } - - // 4th signals - for (auto *publicMember : publicMembers) { - if (publicMember->type != UiPublicMember::Signal) - continue; - - correctOrder.append(publicMember); - } - - // 5th functions - for (auto *source : sourceElements) - correctOrder.append(source); - - // 6th properties - // small script bindings... - for (auto *binding : scriptBindings) - correctOrder.append(binding); - - // ...then large ones - for (auto *binding : largeScriptBinding) - correctOrder.append(binding); - - for (auto *binding : arrayBindings) - correctOrder.append(binding); - - // 7th child objects - for (auto *objectDefinition : objectDefinitions) - correctOrder.append(objectDefinition); - - // 8th all the rest - for (auto *item = node; item != nullptr; item = item->next) { - if (!correctOrder.contains(item->member)) - correctOrder.append(item->member); - } - - // 9th states and transitions - if (states != nullptr) - correctOrder.append(states); - - if (transitions != nullptr) - correctOrder.append(transitions); - - // Rebuild member list from correctOrder - for (auto *item = node; item != nullptr; item = item->next) { - item->member = correctOrder.front(); - correctOrder.pop_front(); - } -} diff --git a/tools/qmlformat/restructureastvisitor.h b/tools/qmlformat/restructureastvisitor.h deleted file mode 100644 index 7b3573300f..0000000000 --- a/tools/qmlformat/restructureastvisitor.h +++ /dev/null @@ -1,47 +0,0 @@ -/**************************************************************************** -** -** 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 RESTRUCTUREASTVISITOR_H -#define RESTRUCTUREASTVISITOR_H - -#include <QtQml/private/qqmljsastvisitor_p.h> -#include <QtQml/private/qqmljsast_p.h> - -using namespace QQmlJS::AST; - -class RestructureAstVisitor : protected Visitor -{ -public: - RestructureAstVisitor(Node *rootNode); - - void throwRecursionDepthError() override {} - - void endVisit(UiObjectMemberList *node) override; -}; - -#endif // RESTRUCTUREASTVISITOR_H diff --git a/tools/qmlimportscanner/.prev_CMakeLists.txt b/tools/qmlimportscanner/.prev_CMakeLists.txt deleted file mode 100644 index 7d56f6b75a..0000000000 --- a/tools/qmlimportscanner/.prev_CMakeLists.txt +++ /dev/null @@ -1,62 +0,0 @@ -# Generated from qmlimportscanner.pro. - -##################################################################### -## qmlimportscanner Tool: -##################################################################### - -qt_get_tool_target_name(target_name qmlimportscanner) -qt_internal_add_tool(${target_name} - TARGET_DESCRIPTION "QML Import Scanner" - SOURCES - main.cpp - DEFINES - QT_NO_CAST_FROM_ASCII - QT_NO_CAST_TO_ASCII - PUBLIC_LIBRARIES - Qt::QmlCompilerPrivate - Qt::QmlDevToolsPrivate -) - -#### Keys ignored in scope 1:.:.:qmlimportscanner.pro:<TRUE>: -# CMAKE_BIN_DIR = "$$cmakeRelativePath($$[QT_HOST_BINS], $$[QT_INSTALL_PREFIX])" -# CMAKE_DEBUG_TYPE = <EMPTY> -# CMAKE_QML_DIR = "$$cmakeRelativePath($$[QT_INSTALL_QML], $$[QT_INSTALL_PREFIX])" -# CMAKE_QML_PLUGIN_SUFFIX_RELEASE = <EMPTY> -# CMAKE_RELEASE_TYPE = <EMPTY> -# QMAKE_TARGET_DESCRIPTION = "QML" "Import" "Scanner" -# _OPTION = "host_build" - -## Scopes: -##################################################################### - -#### Keys ignored in scope 2:.:.:qmlimportscanner.pro:CMAKE_BIN_DIR___contains___^\\.\\./._x_: -# CMAKE_BIN_DIR = "$$[QT_HOST_BINS]/" -# CMAKE_BIN_DIR_IS_ABSOLUTE = "True" - -#### Keys ignored in scope 3:.:.:qmlimportscanner.pro:CMAKE_QML_DIR___contains___^\\.\\./._x_: -# CMAKE_QML_DIR = "$$[QT_INSTALL_QML]/" -# CMAKE_QML_DIR_IS_ABSOLUTE = "True" - -#### Keys ignored in scope 4:.:.:qmlimportscanner.pro:static OR staticlib: -# CMAKE_STATIC_TYPE = "true" - -#### Keys ignored in scope 5:.:.:qmlimportscanner.pro:WIN32: -# CMAKE_QML_PLUGIN_SUFFIX_DEBUG = "d" - -#### Keys ignored in scope 7:.:.:qmlimportscanner.pro:APPLE: -# CMAKE_QML_PLUGIN_SUFFIX_DEBUG = "_debug" - -#### Keys ignored in scope 8:.:.:qmlimportscanner.pro:else: -# CMAKE_QML_PLUGIN_SUFFIX_DEBUG = <EMPTY> - -#### Keys ignored in scope 9:.:.:qmlimportscanner.pro:( QT_FEATURE_debug_and_release OR QT_CONFIG___contains___debug,debug OR release ): -# CMAKE_DEBUG_TYPE = "debug" - -#### Keys ignored in scope 10:.:.:qmlimportscanner.pro:( QT_FEATURE_debug_and_release OR QT_CONFIG___contains___release,debug OR release ): -# CMAKE_RELEASE_TYPE = "release" - -#### Keys ignored in scope 11:.:.:qmlimportscanner.pro:QT_FEATURE_debug_and_release: -# CMAKE_DEBUG_AND_RELEASE = "TRUE" - -#### Keys ignored in scope 12:.:.:qmlimportscanner.pro:else: -# CMAKE_DEBUG_AND_RELEASE = "FALSE" diff --git a/tools/qmlimportscanner/CMakeLists.txt b/tools/qmlimportscanner/CMakeLists.txt index 61e927b6fe..e30490532d 100644 --- a/tools/qmlimportscanner/CMakeLists.txt +++ b/tools/qmlimportscanner/CMakeLists.txt @@ -1,3 +1,6 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + # Generated from qmlimportscanner.pro. ##################################################################### @@ -8,15 +11,17 @@ qt_get_tool_target_name(target_name qmlimportscanner) qt_internal_add_tool(${target_name} TARGET_DESCRIPTION "QML Import Scanner" TOOLS_TARGET Qml # special case + INSTALL_DIR "${INSTALL_LIBEXECDIR}" SOURCES main.cpp DEFINES QT_NO_CAST_FROM_ASCII QT_NO_CAST_TO_ASCII - PUBLIC_LIBRARIES + LIBRARIES Qt::QmlCompilerPrivate - Qt::QmlDevToolsPrivate + Qt::QmlPrivate ) +qt_internal_return_unless_building_tools() # special case begin # Create a dummy package that will just find Qml package, for backwards compatibility reasons. @@ -33,13 +38,18 @@ configure_package_config_file( ) write_basic_package_version_file( - "${config_build_dir}/${INSTALL_CMAKE_NAMESPACE}${target}ConfigVersion.cmake" + "${config_build_dir}/${INSTALL_CMAKE_NAMESPACE}${target}ConfigVersionImpl.cmake" VERSION ${PROJECT_VERSION} COMPATIBILITY AnyNewerVersion ) +qt_internal_write_qt_package_version_file( + "${INSTALL_CMAKE_NAMESPACE}${target}" + "${config_build_dir}/${INSTALL_CMAKE_NAMESPACE}${target}ConfigVersion.cmake" +) qt_install(FILES "${config_build_dir}/${INSTALL_CMAKE_NAMESPACE}${target}Config.cmake" "${config_build_dir}/${INSTALL_CMAKE_NAMESPACE}${target}ConfigVersion.cmake" + "${config_build_dir}/${INSTALL_CMAKE_NAMESPACE}${target}ConfigVersionImpl.cmake" DESTINATION "${config_install_dir}" COMPONENT Devel ) diff --git a/tools/qmlimportscanner/main.cpp b/tools/qmlimportscanner/main.cpp index ea17df921f..7c8f1bc98e 100644 --- a/tools/qmlimportscanner/main.cpp +++ b/tools/qmlimportscanner/main.cpp @@ -1,61 +1,47 @@ -/**************************************************************************** -** -** Copyright (C) 2016 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$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include <private/qqmljslexer_p.h> #include <private/qqmljsparser_p.h> #include <private/qqmljsast_p.h> -#include <private/qv4codegen_p.h> -#include <private/qv4staticvalue_p.h> -#include <private/qqmlirbuilder_p.h> #include <private/qqmljsdiagnosticmessage_p.h> #include <private/qqmldirparser_p.h> #include <private/qqmljsresourcefilemapper_p.h> #include <QtCore/QCoreApplication> +#include <QtCore/QDebug> +#include <QtCore/QDateTime> #include <QtCore/QDir> #include <QtCore/QDirIterator> #include <QtCore/QFile> #include <QtCore/QFileInfo> +#include <QtCore/QHash> #include <QtCore/QSet> #include <QtCore/QStringList> #include <QtCore/QMetaObject> #include <QtCore/QMetaProperty> #include <QtCore/QVariant> +#include <QtCore/QVariantMap> #include <QtCore/QJsonObject> #include <QtCore/QJsonArray> #include <QtCore/QJsonDocument> #include <QtCore/QLibraryInfo> +#include <QtCore/QLoggingCategory> #include <iostream> #include <algorithm> +#include <unordered_map> +#include <unordered_set> QT_USE_NAMESPACE +using namespace Qt::StringLiterals; + +Q_LOGGING_CATEGORY(lcImportScanner, "qt.qml.import.scanner"); +Q_LOGGING_CATEGORY(lcImportScannerFiles, "qt.qml.import.scanner.files"); + +using FileImportsWithoutDepsCache = QHash<QString, QVariantList>; + namespace { QStringList g_qmlImportPaths; @@ -65,27 +51,31 @@ inline QString versionLiteral() { return QStringLiteral("version"); } inline QString nameLiteral() { return QStringLiteral("name"); } inline QString relativePathLiteral() { return QStringLiteral("relativePath"); } inline QString pluginsLiteral() { return QStringLiteral("plugins"); } +inline QString pluginIsOptionalLiteral() { return QStringLiteral("pluginIsOptional"); } inline QString pathLiteral() { return QStringLiteral("path"); } inline QString classnamesLiteral() { return QStringLiteral("classnames"); } inline QString dependenciesLiteral() { return QStringLiteral("dependencies"); } inline QString moduleLiteral() { return QStringLiteral("module"); } inline QString javascriptLiteral() { return QStringLiteral("javascript"); } inline QString directoryLiteral() { return QStringLiteral("directory"); } +inline QString linkTargetLiteral() +{ + return QStringLiteral("linkTarget"); +} +inline QString componentsLiteral() { return QStringLiteral("components"); } +inline QString scriptsLiteral() { return QStringLiteral("scripts"); } +inline QString preferLiteral() { return QStringLiteral("prefer"); } void printUsage(const QString &appNameIn) { - const std::wstring appName = appNameIn.toStdWString(); -#ifndef QT_BOOTSTRAPPED + const std::string appName = appNameIn.toStdString(); const QString qmlPath = QLibraryInfo::path(QLibraryInfo::QmlImportsPath); -#else - const QString qmlPath = QStringLiteral("/home/user/dev/qt-install/qml"); -#endif - std::wcerr + std::cerr << "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" " " << appName << " -qrcFiles file1.qrc file2.qrc -importPath path/to/qt/qml/directory\n\n" "Example: " << appName << " -rootPath . -importPath " - << QDir::toNativeSeparators(qmlPath).toStdWString() + << QDir::toNativeSeparators(qmlPath).toStdString() << '\n'; } @@ -142,8 +132,9 @@ QVariantList findImportsInAst(QQmlJS::AST::UiHeaderItemList *headerItemList, con return imports; } -QVariantList findQmlImportsInQmlFile(const QString &filePath); -QVariantList findQmlImportsInJavascriptFile(const QString &filePath); +QVariantList findQmlImportsInFileWithoutDeps(const QString &filePath, + FileImportsWithoutDepsCache + &fileImportsWithoutDepsCache); static QString versionSuffix(QTypeRevision version) { @@ -153,7 +144,18 @@ static QString versionSuffix(QTypeRevision version) // Read the qmldir file, extract a list of plugins by // parsing the "plugin", "import", and "classname" directives. -QVariantMap pluginsForModulePath(const QString &modulePath, const QString &version) { +QVariantMap pluginsForModulePath(const QString &modulePath, + const QString &version, + FileImportsWithoutDepsCache + &fileImportsWithoutDepsCache) { + using Cache = QHash<QPair<QString, QString>, QVariantMap>; + static Cache pluginsCache; + const QPair<QString, QString> cacheKey = std::make_pair(modulePath, version); + const Cache::const_iterator it = pluginsCache.find(cacheKey); + if (it != pluginsCache.end()) { + return *it; + } + QFile qmldirFile(modulePath + QLatin1String("/qmldir")); if (!qmldirFile.exists()) { qWarning() << "qmldir file not found at" << modulePath; @@ -177,11 +179,27 @@ QVariantMap pluginsForModulePath(const QString &modulePath, const QString &versi QVariantMap pluginInfo; QStringList pluginNameList; + bool isOptional = false; const auto plugins = parser.plugins(); - for (const auto &plugin : plugins) + for (const auto &plugin : plugins) { pluginNameList.append(plugin.name); + isOptional = plugin.optional; + } pluginInfo[pluginsLiteral()] = pluginNameList.join(QLatin1Char(' ')); + if (plugins.size() > 1) { + qWarning() << QStringLiteral("Warning: \"%1\" contains multiple plugin entries. This is discouraged and does not support marking plugins as optional.").arg(modulePath); + isOptional = false; + } + + if (isOptional) { + pluginInfo[pluginIsOptionalLiteral()] = true; + } + + if (!parser.linkTarget().isEmpty()) { + pluginInfo[linkTargetLiteral()] = parser.linkTarget(); + } + pluginInfo[classnamesLiteral()] = parser.classNames().join(QLatin1Char(' ')); QStringList importsAndDependencies; @@ -203,15 +221,23 @@ QVariantMap pluginsForModulePath(const QString &modulePath, const QString &versi } QVariantList importsFromFiles; + QStringList componentFiles; + QStringList scriptFiles; const auto components = parser.components(); for (const auto &component : components) { + const QString componentFullPath = modulePath + QLatin1Char('/') + component.fileName; + componentFiles.append(componentFullPath); importsFromFiles - += findQmlImportsInQmlFile(modulePath + QLatin1Char('/') + component.fileName); + += findQmlImportsInFileWithoutDeps(componentFullPath, + fileImportsWithoutDepsCache); } const auto scripts = parser.scripts(); for (const auto &script : scripts) { + const QString scriptFullPath = modulePath + QLatin1Char('/') + script.fileName; + scriptFiles.append(scriptFullPath); importsFromFiles - += findQmlImportsInJavascriptFile(modulePath + QLatin1Char('/') + script.fileName); + += findQmlImportsInFileWithoutDeps(scriptFullPath, + fileImportsWithoutDepsCache); } for (const QVariant &import : importsFromFiles) { @@ -224,8 +250,23 @@ QVariantMap pluginsForModulePath(const QString &modulePath, const QString &versi version.isEmpty() ? name : (name + QLatin1Char(' ') + version)); } - if (!importsAndDependencies.isEmpty()) + if (!importsAndDependencies.isEmpty()) { + importsAndDependencies.removeDuplicates(); pluginInfo[dependenciesLiteral()] = importsAndDependencies; + } + if (!componentFiles.isEmpty()) { + componentFiles.sort(); + pluginInfo[componentsLiteral()] = componentFiles; + } + if (!scriptFiles.isEmpty()) { + scriptFiles.sort(); + pluginInfo[scriptsLiteral()] = scriptFiles; + } + + if (!parser.preferredPath().isEmpty()) + pluginInfo[preferLiteral()] = parser.preferredPath(); + + pluginsCache.insert(cacheKey, pluginInfo); return pluginInfo; } @@ -238,8 +279,9 @@ QPair<QString, QString> resolveImportPath(const QString &uri, const QString &ver const QStringList parts = uri.split(dot, Qt::SkipEmptyParts); QString ver = version; + QPair<QString, QString> candidate; while (true) { - for (const QString &qmlImportPath : qAsConst(g_qmlImportPaths)) { + for (const QString &qmlImportPath : std::as_const(g_qmlImportPaths)) { // Search for the most specific version first, and search // also for the version in parent modules. For example: // - qml/QtQml/Models.2.0 @@ -252,17 +294,30 @@ QPair<QString, QString> resolveImportPath(const QString &uri, const QString &ver if (relativePath.endsWith(slash)) relativePath.chop(1); const QString candidatePath = QDir::cleanPath(qmlImportPath + slash + relativePath); - if (QDir(candidatePath).exists()) - return qMakePair(candidatePath, relativePath); // import found + const QDir candidateDir(candidatePath); + if (candidateDir.exists()) { + const auto newCandidate = qMakePair(candidatePath, relativePath); // import found + if (candidateDir.exists(u"qmldir"_s)) // if it has a qmldir, we are fine + return newCandidate; + else if (candidate.first.isEmpty()) + candidate = newCandidate; + // otherwise we keep looking if we can find the module again (with a qmldir this time) + } } else { - for (int index = parts.count() - 1; index >= 0; --index) { + for (int index = parts.size() - 1; index >= 0; --index) { QString relativePath = parts.mid(0, index + 1).join(slash) + dot + ver + slash + parts.mid(index + 1).join(slash); if (relativePath.endsWith(slash)) relativePath.chop(1); const QString candidatePath = QDir::cleanPath(qmlImportPath + slash + relativePath); - if (QDir(candidatePath).exists()) - return qMakePair(candidatePath, relativePath); // import found + const QDir candidateDir(candidatePath); + if (candidateDir.exists()) { + const auto newCandidate = qMakePair(candidatePath, relativePath); // import found + if (candidateDir.exists(u"qmldir"_s)) + return newCandidate; + else if (candidate.first.isEmpty()) + candidate = newCandidate; + } } } } @@ -278,58 +333,201 @@ QPair<QString, QString> resolveImportPath(const QString &uri, const QString &ver ver = ver.mid(0, lastDot); } - return QPair<QString, QString>(); // not found + return candidate; } -// Find absolute file system paths and plugins for a list of modules. -QVariantList findPathsForModuleImports(const QVariantList &imports) +// Provides a hasher for module details stored in a QVariantMap disguised as a QVariant.. +// Only supports a subset of types. +struct ImportVariantHasher { + std::size_t operator()(const QVariant &importVariant) const + { + size_t computedHash = 0; + QVariantMap importMap = qvariant_cast<QVariantMap>(importVariant); + for (auto it = importMap.constKeyValueBegin(); it != importMap.constKeyValueEnd(); ++it) { + const QString &key = it->first; + const QVariant &value = it->second; + + if (!value.isValid() || value.isNull()) { + computedHash = qHashMulti(computedHash, key, 0); + continue; + } + + const auto valueTypeId = value.typeId(); + switch (valueTypeId) { + case QMetaType::QString: + computedHash = qHashMulti(computedHash, key, value.toString()); + break; + case QMetaType::Bool: + computedHash = qHashMulti(computedHash, key, value.toBool()); + break; + case QMetaType::QStringList: + computedHash = qHashMulti(computedHash, key, value.toStringList()); + break; + default: + Q_ASSERT_X(valueTypeId, "ImportVariantHasher", "Invalid variant type detected"); + break; + } + } + + return computedHash; + } +}; + +using ImportDetailsAndDeps = QPair<QVariantMap, QStringList>; + +// Returns the import information as it will be written out to the json / .cmake file. +// The dependencies are not stored in the same QVariantMap because we don't currently need that +// information in the output file. +ImportDetailsAndDeps +getImportDetails(const QVariant &inputImport, + FileImportsWithoutDepsCache &fileImportsWithoutDepsCache) { + + using Cache = std::unordered_map<QVariant, ImportDetailsAndDeps, ImportVariantHasher>; + static Cache cache; + + const Cache::const_iterator it = cache.find(inputImport); + if (it != cache.end()) { + return it->second; + } + + QVariantMap import = qvariant_cast<QVariantMap>(inputImport); + QStringList dependencies; + if (import.value(typeLiteral()) == moduleLiteral()) { + const QString version = import.value(versionLiteral()).toString(); + const QPair<QString, QString> paths = + resolveImportPath(import.value(nameLiteral()).toString(), version); + QVariantMap plugininfo; + if (!paths.first.isEmpty()) { + import.insert(pathLiteral(), paths.first); + import.insert(relativePathLiteral(), paths.second); + plugininfo = pluginsForModulePath(paths.first, + version, + fileImportsWithoutDepsCache); + } + QString linkTarget = plugininfo.value(linkTargetLiteral()).toString(); + QString plugins = plugininfo.value(pluginsLiteral()).toString(); + bool isOptional = plugininfo.value(pluginIsOptionalLiteral(), QVariant(false)).toBool(); + QString classnames = plugininfo.value(classnamesLiteral()).toString(); + QStringList components = plugininfo.value(componentsLiteral()).toStringList(); + QStringList scripts = plugininfo.value(scriptsLiteral()).toStringList(); + QString prefer = plugininfo.value(preferLiteral()).toString(); + if (!linkTarget.isEmpty()) + import.insert(linkTargetLiteral(), linkTarget); + if (!plugins.isEmpty()) + import.insert(QStringLiteral("plugin"), plugins); + if (isOptional) + import.insert(pluginIsOptionalLiteral(), true); + if (!classnames.isEmpty()) + import.insert(QStringLiteral("classname"), classnames); + if (plugininfo.contains(dependenciesLiteral())) { + dependencies = plugininfo.value(dependenciesLiteral()).toStringList(); + } + if (!components.isEmpty()) { + components.removeDuplicates(); + import.insert(componentsLiteral(), components); + } + if (!scripts.isEmpty()) { + scripts.removeDuplicates(); + import.insert(scriptsLiteral(), scripts); + } + if (!prefer.isEmpty()) { + import.insert(preferLiteral(), prefer); + } + } + import.remove(versionLiteral()); + + const ImportDetailsAndDeps result = {import, dependencies}; + cache.insert({inputImport, result}); + return result; +} + +// Parse a dependency string line into a QVariantMap, to be used as a key when processing imports +// in getGetDetailedModuleImportsIncludingDependencies. +QVariantMap dependencyStringToImport(const QString &line) { + const auto dep = QStringView{line}.split(QLatin1Char(' '), Qt::SkipEmptyParts); + const QString name = dep[0].toString(); + QVariantMap depImport; + depImport[typeLiteral()] = moduleLiteral(); + depImport[nameLiteral()] = name; + if (dep.size() > 1) + depImport[versionLiteral()] = dep[1].toString(); + return depImport; +} + +// Returns details of given input import and its recursive module dependencies. +// The details include absolute file system paths for the the module plugin, components, +// etc. +// An internal cache is used to prevent repeated computation for the same input module. +QVariantList getGetDetailedModuleImportsIncludingDependencies( + const QVariant &inputImport, + FileImportsWithoutDepsCache &fileImportsWithoutDepsCache) { + using Cache = std::unordered_map<QVariant, QVariantList, ImportVariantHasher>; + static Cache importsCacheWithDeps; + + const Cache::const_iterator it = importsCacheWithDeps.find(inputImport); + if (it != importsCacheWithDeps.end()) { + return it->second; + } + QVariantList done; - QVariantList importsCopy(imports); - - for (int i = 0; i < importsCopy.length(); ++i) { - QVariantMap import = qvariant_cast<QVariantMap>(importsCopy.at(i)); - if (import.value(typeLiteral()) == moduleLiteral()) { - const QString version = import.value(versionLiteral()).toString(); - const QPair<QString, QString> paths = - resolveImportPath(import.value(nameLiteral()).toString(), version); - QVariantMap plugininfo; - if (!paths.first.isEmpty()) { - import.insert(pathLiteral(), paths.first); - import.insert(relativePathLiteral(), paths.second); - plugininfo = pluginsForModulePath(paths.first, version); - } - QString plugins = plugininfo.value(pluginsLiteral()).toString(); - QString classnames = plugininfo.value(classnamesLiteral()).toString(); - if (!plugins.isEmpty()) - import.insert(QStringLiteral("plugin"), plugins); - if (!classnames.isEmpty()) - import.insert(QStringLiteral("classname"), classnames); - if (plugininfo.contains(dependenciesLiteral())) { - const QStringList dependencies = plugininfo.value(dependenciesLiteral()).toStringList(); - for (const QString &line : dependencies) { - const auto dep = QStringView{line}.split(QLatin1Char(' '), Qt::SkipEmptyParts); - const QString name = dep[0].toString(); - QVariantMap depImport; - depImport[typeLiteral()] = moduleLiteral(); - depImport[nameLiteral()] = name; - if (dep.length() > 1) - depImport[versionLiteral()] = dep[1].toString(); - - if (!importsCopy.contains(depImport)) - importsCopy.append(depImport); + QVariantList importsToProcess; + std::unordered_set<QVariant, ImportVariantHasher> importsSeen; + importsToProcess.append(inputImport); + + for (int i = 0; i < importsToProcess.size(); ++i) { + const QVariant importToProcess = importsToProcess.at(i); + auto [details, deps] = getImportDetails(importToProcess, fileImportsWithoutDepsCache); + if (details.value(typeLiteral()) == moduleLiteral()) { + for (const QString &line : deps) { + const QVariantMap depImport = dependencyStringToImport(line); + + // Skip self-dependencies. + if (depImport == importToProcess) + continue; + + if (importsSeen.find(depImport) == importsSeen.end()) { + importsToProcess.append(depImport); + importsSeen.insert(depImport); } } } - import.remove(versionLiteral()); - done.append(import); + done.append(details); } + + importsCacheWithDeps.insert({inputImport, done}); return done; } +QVariantList mergeImports(const QVariantList &a, const QVariantList &b); + +// Returns details of given input imports and their recursive module dependencies. +QVariantList getGetDetailedModuleImportsIncludingDependencies( + const QVariantList &inputImports, + FileImportsWithoutDepsCache &fileImportsWithoutDepsCache) +{ + QVariantList result; + + // Get rid of duplicates in input module list. + QVariantList inputImportsCopy; + inputImportsCopy = mergeImports(inputImportsCopy, inputImports); + + // Collect recursive dependencies for each input module and merge into result, discarding + // duplicates. + for (auto it = inputImportsCopy.begin(); it != inputImportsCopy.end(); ++it) { + QVariantList imports = getGetDetailedModuleImportsIncludingDependencies( + *it, fileImportsWithoutDepsCache); + result = mergeImports(result, imports); + } + return result; +} + // Scan a single qml file for import statements QVariantList findQmlImportsInQmlCode(const QString &filePath, const QString &code) { + qCDebug(lcImportScannerFiles) << "Parsing code and finding imports in" << filePath + << "TS:" << QDateTime::currentMSecsSinceEpoch(); + QQmlJS::Engine engine; QQmlJS::Lexer lexer(&engine); lexer.setCode(code, /*line = */ 1); @@ -425,9 +623,17 @@ QVariantList findQmlImportsInJavascriptFile(const QString &filePath) return collector.imports; } -// Scan a single qml or js file for import statements -QVariantList findQmlImportsInFile(const QString &filePath) +// Scan a single qml or js file for import statements without resolving dependencies. +QVariantList findQmlImportsInFileWithoutDeps(const QString &filePath, + FileImportsWithoutDepsCache + &fileImportsWithoutDepsCache) { + const FileImportsWithoutDepsCache::const_iterator it = + fileImportsWithoutDepsCache.find(filePath); + if (it != fileImportsWithoutDepsCache.end()) { + return *it; + } + QVariantList imports; if (filePath == QLatin1String("-")) { QFile f; @@ -437,12 +643,47 @@ QVariantList findQmlImportsInFile(const QString &filePath) imports = findQmlImportsInQmlFile(filePath); } else if (filePath.endsWith(QLatin1String(".js"))) { imports = findQmlImportsInJavascriptFile(filePath); + } else { + qCDebug(lcImportScanner) << "Skipping file because it's not a .qml/.js file"; + return imports; } - return findPathsForModuleImports(imports); + fileImportsWithoutDepsCache.insert(filePath, imports); + return imports; +} + +// Scan a single qml or js file for import statements, resolve dependencies and return the full +// list of modules the file depends on. +QVariantList findQmlImportsInFile(const QString &filePath, + FileImportsWithoutDepsCache + &fileImportsWithoutDepsCache) { + const auto fileProcessTimeBegin = QDateTime::currentDateTime(); + + QVariantList imports = findQmlImportsInFileWithoutDeps(filePath, + fileImportsWithoutDepsCache); + if (imports.empty()) + return imports; + + const auto pathsTimeBegin = QDateTime::currentDateTime(); + + qCDebug(lcImportScanner) << "Finding module paths for imported modules in" << filePath + << "TS:" << pathsTimeBegin.toMSecsSinceEpoch(); + QVariantList importPaths = getGetDetailedModuleImportsIncludingDependencies( + imports, fileImportsWithoutDepsCache); + + const auto pathsTimeEnd = QDateTime::currentDateTime(); + const auto duration = pathsTimeBegin.msecsTo(pathsTimeEnd); + const auto fileProcessingDuration = fileProcessTimeBegin.msecsTo(pathsTimeEnd); + qCDebug(lcImportScanner) << "Found module paths:" << importPaths.size() + << "TS:" << pathsTimeEnd.toMSecsSinceEpoch() + << "Path resolution duration:" << duration << "msecs"; + qCDebug(lcImportScanner) << "Scan duration:" << fileProcessingDuration << "msecs"; + return importPaths; } // Merge two lists of imports, discard duplicates. +// Empirical tests show that for a small amount of values, the n^2 QVariantList comparison +// is still faster than using an unordered_set + hashing a complex QVariantMap. QVariantList mergeImports(const QVariantList &a, const QVariantList &b) { QVariantList merged = a; @@ -472,7 +713,9 @@ struct pathStartsWith { // Scan all qml files in directory for import statements -QVariantList findQmlImportsInDirectory(const QString &qmlDir) +QVariantList findQmlImportsInDirectory(const QString &qmlDir, + FileImportsWithoutDepsCache + &fileImportsWithoutDepsCache) { QVariantList ret; if (qmlDir.isEmpty()) @@ -506,8 +749,15 @@ QVariantList findQmlImportsInDirectory(const QString &qmlDir) } for (const QFileInfo &x : entries) - if (x.isFile()) - ret = mergeImports(ret, findQmlImportsInFile(x.absoluteFilePath())); + if (x.isFile()) { + const auto entryAbsolutePath = x.absoluteFilePath(); + qCDebug(lcImportScanner) << "Scanning file" << entryAbsolutePath + << "TS:" << QDateTime::currentMSecsSinceEpoch(); + ret = mergeImports(ret, + findQmlImportsInFile( + entryAbsolutePath, + fileImportsWithoutDepsCache)); + } } return ret; } @@ -515,19 +765,29 @@ QVariantList findQmlImportsInDirectory(const QString &qmlDir) // Find qml imports recursively from a root set of qml files. // The directories in qmlDirs are searched recursively. // The files in qmlFiles parsed directly. -QVariantList findQmlImportsRecursively(const QStringList &qmlDirs, const QStringList &scanFiles) +QVariantList findQmlImportsRecursively(const QStringList &qmlDirs, + const QStringList &scanFiles, + FileImportsWithoutDepsCache + &fileImportsWithoutDepsCache) { QVariantList ret; + qCDebug(lcImportScanner) << "Scanning" << qmlDirs.size() << "root directories and" + << scanFiles.size() << "files."; + // Scan all app root qml directories for imports for (const QString &qmlDir : qmlDirs) { - QVariantList imports = findQmlImportsInDirectory(qmlDir); + qCDebug(lcImportScanner) << "Scanning root" << qmlDir + << "TS:" << QDateTime::currentMSecsSinceEpoch(); + QVariantList imports = findQmlImportsInDirectory(qmlDir, fileImportsWithoutDepsCache); ret = mergeImports(ret, imports); } // Scan app qml files for imports for (const QString &file : scanFiles) { - QVariantList imports = findQmlImportsInFile(file); + qCDebug(lcImportScanner) << "Scanning file" << file + << "TS:" << QDateTime::currentMSecsSinceEpoch(); + QVariantList imports = findQmlImportsInFile(file, fileImportsWithoutDepsCache); ret = mergeImports(ret, imports); } @@ -550,8 +810,20 @@ QString generateCmakeIncludeFileContent(const QVariantList &importList) { 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 << it.key().toUpper() << QLatin1Char(';'); + // QVariant can implicitly convert QString to the QStringList with the single + // element, let's use this. + QStringList args = it.value().toStringList(); + if (args.isEmpty()) { + // This should not happen, but if it does, the result of the + // 'cmake_parse_arguments' call will be incorrect, so follow up semicolon + // indicates that the single-/multiarg option is empty. + s << QLatin1Char(';'); + } else { + for (auto arg : args) { + s << arg << QLatin1Char(';'); + } + } } s << QStringLiteral("\")\n"); ++importsCount; @@ -564,59 +836,102 @@ QString generateCmakeIncludeFileContent(const QVariantList &importList) { return content; } +bool argumentsFromCommandLineAndFile(QStringList &allArguments, const QStringList &arguments) +{ + allArguments.reserve(arguments.size()); + for (const QString &argument : arguments) { + // "@file" doesn't start with a '-' so we can't use QCommandLineParser for it + if (argument.startsWith(QLatin1Char('@'))) { + QString optionsFile = argument; + optionsFile.remove(0, 1); + if (optionsFile.isEmpty()) { + fprintf(stderr, "The @ option requires an input file"); + return false; + } + QFile f(optionsFile); + if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) { + fprintf(stderr, "Cannot open options file specified with @"); + return false; + } + while (!f.atEnd()) { + QString line = QString::fromLocal8Bit(f.readLine().trimmed()); + if (!line.isEmpty()) + allArguments << line; + } + } else { + allArguments << argument; + } + } + return true; +} + } // namespace int main(int argc, char *argv[]) { QCoreApplication app(argc, argv); QCoreApplication::setApplicationVersion(QLatin1String(QT_VERSION_STR)); - QStringList args = app.arguments(); + QStringList args; + if (!argumentsFromCommandLineAndFile(args, app.arguments())) + return EXIT_FAILURE; const QString appName = QFileInfo(app.applicationFilePath()).baseName(); if (args.size() < 2) { printUsage(appName); return 1; } + // QQmlDirParser returnes QMultiHashes. Ensure deterministic output. + QHashSeed::setDeterministicGlobalSeed(); + QStringList qmlRootPaths; QStringList scanFiles; QStringList qmlImportPaths; QStringList qrcFiles; bool generateCmakeContent = false; + QString outputFile; int i = 1; - while (i < args.count()) { + while (i < args.size()) { const QString &arg = args.at(i); ++i; QStringList *argReceiver = nullptr; if (!arg.startsWith(QLatin1Char('-')) || arg == QLatin1String("-")) { qmlRootPaths += arg; } else if (arg == QLatin1String("-rootPath")) { - if (i >= args.count()) + if (i >= args.size()) std::cerr << "-rootPath requires an argument\n"; argReceiver = &qmlRootPaths; } else if (arg == QLatin1String("-qmlFiles")) { - if (i >= args.count()) + if (i >= args.size()) std::cerr << "-qmlFiles requires an argument\n"; argReceiver = &scanFiles; } else if (arg == QLatin1String("-jsFiles")) { - if (i >= args.count()) + if (i >= args.size()) std::cerr << "-jsFiles requires an argument\n"; argReceiver = &scanFiles; } else if (arg == QLatin1String("-importPath")) { - if (i >= args.count()) + if (i >= args.size()) 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 if (arg == QLatin1String("-output-file")) { + if (i >= args.size()) { + std::cerr << "-output-file requires an argument\n"; + return 1; + } + outputFile = args.at(i); + ++i; + continue; } else { std::cerr << qPrintable(appName) << ": Invalid argument: \"" << qPrintable(arg) << "\"\n"; return 1; } - while (i < args.count()) { + while (i < args.size()) { const QString arg = args.at(i); if (arg.startsWith(QLatin1Char('-')) && arg != QLatin1String("-")) break; @@ -625,8 +940,12 @@ int main(int argc, char *argv[]) std::cerr << qPrintable(appName) << ": No such file or directory: \"" << qPrintable(arg) << "\"\n"; return 1; - } else { + } else if (argReceiver) { *argReceiver += arg; + } else { + std::cerr << qPrintable(appName) << ": Invalid argument: \"" + << qPrintable(arg) << "\"\n"; + return 1; } } } @@ -638,8 +957,13 @@ int main(int argc, char *argv[]) g_qmlImportPaths = qmlImportPaths; + FileImportsWithoutDepsCache fileImportsWithoutDepsCache; + // Find the imports! - QVariantList imports = findQmlImportsRecursively(qmlRootPaths, scanFiles); + QVariantList imports = findQmlImportsRecursively(qmlRootPaths, + scanFiles, + fileImportsWithoutDepsCache + ); QByteArray content; if (generateCmakeContent) { @@ -650,6 +974,17 @@ int main(int argc, char *argv[]) content = QJsonDocument(QJsonArray::fromVariantList(imports)).toJson(); } - std::cout << content.constData() << std::endl; + if (outputFile.isEmpty()) { + std::cout << content.constData() << std::endl; + } else { + QFile f(outputFile); + if (!f.open(QIODevice::WriteOnly | QIODevice::Text)) { + std::cerr << qPrintable(appName) << ": Unable to write to output file: \"" + << qPrintable(outputFile) << "\"\n"; + return 1; + } + QTextStream out(&f); + out << content << "\n"; + } return 0; } diff --git a/tools/qmljs/.prev_CMakeLists.txt b/tools/qmljs/.prev_CMakeLists.txt deleted file mode 100644 index 86d0a31552..0000000000 --- a/tools/qmljs/.prev_CMakeLists.txt +++ /dev/null @@ -1,89 +0,0 @@ -# Generated from qmljs.pro. - -##################################################################### -## qmljs Tool: -##################################################################### - -qt_get_tool_target_name(target_name qmljs) -qt_internal_add_tool(${target_name} - TARGET_DESCRIPTION "QML JavaScript Tool" - SOURCES - qmljs.cpp - DEFINES - BUILDING_QT__ - ENABLE_ASSEMBLER_WX_EXCLUSIVE=1 - ENABLE_DFG_JIT=0 - ENABLE_DFG_JIT_UTILITY_METHODS=1 - ENABLE_JIT_CONSTANT_BLINDING=0 - ENABLE_LLINT=0 - JS_EXPORT_PRIVATE="" - WTFInvokeCrashHook=qmlWTFInvokeCrashHook - WTFReportAssertionFailure=qmlWTFReportAssertionFailure - WTFReportAssertionFailureWithMessage=qmlWTFReportAssertionFailureWithMessage - WTFReportBacktrace=qmlWTFReportBacktrace - WTF_EXPORT_PRIVATE="" - INCLUDE_DIRECTORIES - ../../src/3rdparty/masm - ../../src/3rdparty/masm/assembler - ../../src/3rdparty/masm/disassembler - ../../src/3rdparty/masm/disassembler/udis86 - ../../src/3rdparty/masm/jit - ../../src/3rdparty/masm/runtime - ../../src/3rdparty/masm/stubs - ../../src/3rdparty/masm/stubs/runtime - ../../src/3rdparty/masm/stubs/wtf - ../../src/3rdparty/masm/wtf - PUBLIC_LIBRARIES - Qt::CorePrivate - Qt::QmlPrivate -) - -#### Keys ignored in scope 1:.:.:qmljs.pro:<TRUE>: -# QMAKE_TARGET_DESCRIPTION = "QML" "JavaScript" "Tool" -# TEMPLATE = "app" - -## Scopes: -##################################################################### - -qt_internal_extend_target(${target_name} CONDITION WIN32 - DEFINES - NOMINMAX -) - -qt_internal_extend_target(${target_name} CONDITION disassembler AND ((TEST_architecture_arch STREQUAL "i386") OR (TEST_architecture_arch STREQUAL "x86_64")) - DEFINES - WTF_USE_UDIS86=1 -) - -qt_internal_extend_target(${target_name} CONDITION (TEST_architecture_arch STREQUAL "arm") AND disassembler - DEFINES - WTF_USE_ARMV7_DISASSEMBLER=1 -) - -qt_internal_extend_target(${target_name} CONDITION (TEST_architecture_arch STREQUAL "arm64") AND disassembler - DEFINES - WTF_USE_ARM64_DISASSEMBLER=1 -) - -qt_internal_extend_target(${target_name} CONDITION (TEST_architecture_arch STREQUAL "mips") AND disassembler - DEFINES - WTF_USE_MIPS32_DISASSEMBLER=1 -) - -qt_internal_extend_target(${target_name} CONDITION NOT disassembler - DEFINES - WTF_USE_UDIS86=0 -) - -qt_internal_extend_target(${target_name} CONDITION CMAKE_BUILD_TYPE STREQUAL Release - DEFINES - NDEBUG -) - -qt_internal_extend_target(${target_name} CONDITION GCC AND QT_COMPILER_VERSION_MAJOR STRGREATER 6 AND NOT CLANG AND NOT ICC - COMPILE_OPTIONS - -Wno-expansion-to-defined -) - -#### Keys ignored in scope 12:.:../../src/3rdparty/masm:../../src/3rdparty/masm/masm-defs.pri:(QT_COMPILER_VERSION_MAJOR STRGREATER 6): -# QMAKE_CXXFLAGS_WARN_ON = "-Wno-expansion-to-defined" diff --git a/tools/qmljs/CMakeLists.txt b/tools/qmljs/CMakeLists.txt index 147ddf008c..40faa5d013 100644 --- a/tools/qmljs/CMakeLists.txt +++ b/tools/qmljs/CMakeLists.txt @@ -1,13 +1,14 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + # Generated from qmljs.pro. ##################################################################### -## qmljs Tool: +## qmljs App: ##################################################################### -qt_get_tool_target_name(target_name qmljs) -qt_internal_add_tool(${target_name} +qt_internal_add_app(qmljs TARGET_DESCRIPTION "QML JavaScript Tool" - TOOLS_TARGET Qml # special case SOURCES qmljs.cpp DEFINES @@ -34,11 +35,13 @@ qt_internal_add_tool(${target_name} ../../src/3rdparty/masm/stubs/runtime ../../src/3rdparty/masm/stubs/wtf ../../src/3rdparty/masm/wtf - PUBLIC_LIBRARIES + LIBRARIES Qt::CorePrivate Qt::QmlPrivate ) +set_target_properties(qmljs PROPERTIES WIN32_EXECUTABLE FALSE) + #### Keys ignored in scope 1:.:.:qmljs.pro:<TRUE>: # QMAKE_TARGET_DESCRIPTION = "QML" "JavaScript" "Tool" # TEMPLATE = "app" @@ -46,42 +49,37 @@ qt_internal_add_tool(${target_name} ## Scopes: ##################################################################### -qt_internal_extend_target(${target_name} CONDITION WIN32 - DEFINES - NOMINMAX -) - -qt_internal_extend_target(${target_name} CONDITION disassembler AND ((TEST_architecture_arch STREQUAL "i386") OR (TEST_architecture_arch STREQUAL "x86_64")) +qt_internal_extend_target(qmljs CONDITION disassembler AND ((TEST_architecture_arch STREQUAL "i386") OR (TEST_architecture_arch STREQUAL "x86_64")) DEFINES WTF_USE_UDIS86=1 ) -qt_internal_extend_target(${target_name} CONDITION (TEST_architecture_arch STREQUAL "arm") AND disassembler +qt_internal_extend_target(qmljs CONDITION (TEST_architecture_arch STREQUAL "arm") AND disassembler DEFINES WTF_USE_ARMV7_DISASSEMBLER=1 ) -qt_internal_extend_target(${target_name} CONDITION (TEST_architecture_arch STREQUAL "arm64") AND disassembler +qt_internal_extend_target(qmljs CONDITION (TEST_architecture_arch STREQUAL "arm64") AND disassembler DEFINES WTF_USE_ARM64_DISASSEMBLER=1 ) -qt_internal_extend_target(${target_name} CONDITION (TEST_architecture_arch STREQUAL "mips") AND disassembler +qt_internal_extend_target(qmljs CONDITION (TEST_architecture_arch STREQUAL "mips") AND disassembler DEFINES WTF_USE_MIPS32_DISASSEMBLER=1 ) -qt_internal_extend_target(${target_name} CONDITION NOT disassembler +qt_internal_extend_target(qmljs CONDITION NOT disassembler DEFINES WTF_USE_UDIS86=0 ) -qt_internal_extend_target(${target_name} CONDITION CMAKE_BUILD_TYPE STREQUAL Release +qt_internal_extend_target(qmljs CONDITION CMAKE_BUILD_TYPE STREQUAL Release DEFINES NDEBUG ) -qt_internal_extend_target(${target_name} CONDITION GCC AND QT_COMPILER_VERSION_MAJOR STRGREATER 6 AND NOT CLANG AND NOT ICC +qt_internal_extend_target(qmljs CONDITION GCC AND QT_COMPILER_VERSION_MAJOR STRGREATER 6 AND NOT CLANG AND NOT ICC COMPILE_OPTIONS -Wno-expansion-to-defined ) diff --git a/tools/qmljs/qmljs.cpp b/tools/qmljs/qmljs.cpp index ba5e5f553c..8a1c4d0ded 100644 --- a/tools/qmljs/qmljs.cpp +++ b/tools/qmljs/qmljs.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the V4VM module 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$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "private/qv4object_p.h" #include "private/qv4runtime_p.h" @@ -44,6 +19,7 @@ #include <QtCore/QFile> #include <QtCore/QFileInfo> #include <QtCore/QDateTime> +#include <QtCore/qcommandlineparser.h> #include <private/qqmljsengine_p.h> #include <private/qqmljslexer_p.h> #include <private/qqmljsparser_p.h> @@ -74,43 +50,48 @@ int main(int argc, char *argv[]) { QCoreApplication app(argc, argv); QCoreApplication::setApplicationVersion(QLatin1String(QT_VERSION_STR)); - QStringList args = app.arguments(); - args.removeFirst(); - bool runAsQml = false; - bool runAsModule = false; - bool cache = false; + QCommandLineParser parser; + parser.addHelpOption(); + parser.setApplicationDescription("Utility to execute scripts in QML's V4 engine"); + parser.addVersionOption(); + parser.addPositionalArgument("files", "Files to execute.", "[files...]"); - if (!args.isEmpty()) { - if (args.constFirst() == QLatin1String("--jit")) { - qputenv("QV4_JIT_CALL_THRESHOLD", QByteArray("0")); - args.removeFirst(); - } - if (args.constFirst() == QLatin1String("--interpret")) { - qputenv("QV4_FORCE_INTERPRETER", QByteArray("1")); - args.removeFirst(); - } - if (args.constFirst() == QLatin1String("--qml")) { - runAsQml = true; - args.removeFirst(); - } + QCommandLineOption forceJit("jit", "Force JIT."); + parser.addOption(forceJit); - if (args.constFirst() == QLatin1String("--module")) { - runAsModule = true; - args.removeFirst(); - } + QCommandLineOption forceInterpreter("interpret", "Force interpreter."); + parser.addOption(forceInterpreter); - if (args.constFirst() == QLatin1String("--cache")) { - cache = true; - args.removeFirst(); - } + QCommandLineOption qml("qml", "Run as QML."); + parser.addOption(qml); + + QCommandLineOption module("module", "Run as Module."); + parser.addOption(module); - if (args.constFirst() == QLatin1String("--help")) { - std::cerr << "Usage: qmljs [|--jit|--interpret|--qml] file..." << std::endl; - return EXIT_SUCCESS; + QCommandLineOption cache("cache", "Use cache."); + parser.addOption(cache); + + parser.process(app); + + bool jitEnabled = false; + + if (parser.isSet(forceJit)) { + qputenv("QV4_JIT_CALL_THRESHOLD", QByteArray("0")); + jitEnabled = true; + } + if (parser.isSet(forceInterpreter)) { + qputenv("QV4_FORCE_INTERPRETER", QByteArray("1")); + if (jitEnabled) { + std::cerr << "You cannot use 'Force JIT' and 'Force Interpreter' at the same time."; + return EXIT_FAILURE; } } + const bool runAsQml = parser.isSet(qml); + const bool runAsModule = parser.isSet(module); + const bool useCache = parser.isSet(cache); + const QStringList args = parser.positionalArguments(); QV4::ExecutionEngine vm; @@ -119,13 +100,15 @@ int main(int argc, char *argv[]) QV4::GlobalExtensions::init(vm.globalObject, QJSEngine::ConsoleExtension | QJSEngine::GarbageCollectionExtension); - for (const QString &fn : qAsConst(args)) { + for (const QString &fn : args) { QV4::ScopedValue result(scope); if (runAsModule) { - auto moduleUnit = vm.loadModule(QUrl::fromLocalFile(QFileInfo(fn).absoluteFilePath())); - if (moduleUnit) { - if (moduleUnit->instantiate(&vm)) - moduleUnit->evaluate(); + auto module = vm.loadModule(QUrl::fromLocalFile(QFileInfo(fn).absoluteFilePath())); + if (module.compiled) { + if (module.compiled->instantiate()) + module.compiled->evaluate(); + } else if (module.native) { + // Nothing to do. Native modules have no global code. } else { vm.throwError(QStringLiteral("Could not load module file")); } @@ -136,33 +119,34 @@ int main(int argc, char *argv[]) return EXIT_FAILURE; } QScopedPointer<QV4::Script> script; - if (cache && QFile::exists(fn + QLatin1Char('c'))) { - QQmlRefPointer<QV4::ExecutableCompilationUnit> unit - = QV4::ExecutableCompilationUnit::create(); + if (useCache && QFile::exists(fn + QLatin1Char('c'))) { + auto unit = QQml::makeRefPointer<QV4::CompiledData::CompilationUnit>(); QString error; if (unit->loadFromDisk(QUrl::fromLocalFile(fn), QFileInfo(fn).lastModified(), &error)) { - script.reset(new QV4::Script(&vm, nullptr, unit)); + script.reset(new QV4::Script( + &vm, nullptr, vm.insertCompilationUnit(std::move(unit)))); } else { std::cout << "Error loading" << qPrintable(fn) << "from disk cache:" << qPrintable(error) << std::endl; } } if (!script) { QByteArray ba = file.readAll(); - const QString code = QString::fromUtf8(ba.constData(), ba.length()); + const QString code = QString::fromUtf8(ba.constData(), ba.size()); file.close(); script.reset(new QV4::Script(ctx, QV4::Compiler::ContextType::Global, code, fn)); script->parseAsBinding = runAsQml; script->parse(); } - if (!scope.engine->hasException) { + if (!scope.hasException()) { const auto unit = script->compilationUnit; - if (cache && unit && !(unit->unitData()->flags & QV4::CompiledData::Unit::StaticData)) { + if (useCache && unit && !(unit->unitData()->flags & QV4::CompiledData::Unit::StaticData)) { if (unit->unitData()->sourceTimeStamp == 0) { const_cast<QV4::CompiledData::Unit*>(unit->unitData())->sourceTimeStamp = QFileInfo(fn).lastModified().toMSecsSinceEpoch(); } QString saveError; - if (!unit->saveToDisk(QUrl::fromLocalFile(fn), &saveError)) { + if (!unit->baseCompilationUnit()->saveToDisk( + QUrl::fromLocalFile(fn), &saveError)) { std::cout << "Error saving JS cache file: " << qPrintable(saveError) << std::endl; } } @@ -171,7 +155,7 @@ int main(int argc, char *argv[]) // std::cout << t.elapsed() << " ms. elapsed" << std::endl; } } - if (scope.engine->hasException) { + if (scope.hasException()) { QV4::StackTrace trace; QV4::ScopedValue ex(scope, scope.engine->catchException(&trace)); showException(ctx, ex, trace); diff --git a/tools/qmljsrootgen/CMakeLists.txt b/tools/qmljsrootgen/CMakeLists.txt new file mode 100644 index 0000000000..c81494b664 --- /dev/null +++ b/tools/qmljsrootgen/CMakeLists.txt @@ -0,0 +1,15 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +qt_get_tool_target_name(target_name qmljsrootgen) +qt_internal_add_tool(${target_name} + TARGET_DESCRIPTION "QML Global Object Metatypes Generator" + TOOLS_TARGET Qml # special case + INSTALL_DIR "${INSTALL_LIBEXECDIR}" + SOURCES + main.cpp + LIBRARIES + Qt::CorePrivate + Qt::QmlPrivate +) +qt_internal_return_unless_building_tools() diff --git a/tools/qmljsrootgen/main.cpp b/tools/qmljsrootgen/main.cpp new file mode 100644 index 0000000000..acd375144e --- /dev/null +++ b/tools/qmljsrootgen/main.cpp @@ -0,0 +1,407 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include <QtQml/private/qjsvalue_p.h> +#include <QtQml/private/qv4propertykey_p.h> +#include <QtQml/private/qv4global_p.h> +#include <QtQml/private/qv4functionobject_p.h> +#include <QtQml/qjsengine.h> +#include <QtQml/qjsmanagedvalue.h> + +#include <QtCore/qcoreapplication.h> +#include <QtCore/qfile.h> +#include <QtCore/qcommandlineparser.h> + +#include <QtCore/qjsondocument.h> +#include <QtCore/qjsonarray.h> +#include <QtCore/qjsonobject.h> + +struct PropertyInfo +{ + QString name; + bool writable; +}; + +static QV4::ReturnedValue asManaged(const QJSManagedValue &value) +{ + const QJSValue jsVal = value.toJSValue(); + const QV4::Managed *managed = QJSValuePrivate::asManagedType<QV4::Managed>(&jsVal); + return managed ? managed->asReturnedValue() : QV4::Encode::undefined(); +} + +static QJSManagedValue checkedProperty(const QJSManagedValue &value, const QString &name) +{ + return value.hasProperty(name) ? QJSManagedValue(value.property(name), value.engine()) + : QJSManagedValue(QJSPrimitiveUndefined(), value.engine()); +} + +QList<PropertyInfo> getPropertyInfos(const QJSManagedValue &value) +{ + QV4::Scope scope(value.engine()->handle()); + QV4::ScopedObject scoped(scope, asManaged(value)); + if (!scoped) + return {}; + + QList<PropertyInfo> infos; + + QScopedPointer<QV4::OwnPropertyKeyIterator> iterator(scoped->ownPropertyKeys(scoped)); + QV4::Scoped<QV4::InternalClass> internalClass(scope, scoped->internalClass()); + + for (auto key = iterator->next(scoped); key.isValid(); key = iterator->next(scoped)) { + if (key.isSymbol()) + continue; + + const auto *entry = internalClass->d()->propertyTable.lookup(key); + infos.append({ + key.toQString(), + !entry || internalClass->d()->propertyData.at(entry->index).isWritable() + }); + }; + + return infos; +} + +struct State { + QMap<QString, QJSValue> constructors; + QMap<QString, QJSValue> prototypes; + QSet<QString> primitives; +}; + +static QString buildConstructor(const QJSManagedValue &constructor, QJsonArray *classes, + State *seen, const QString &name, QJSManagedValue *constructed); + +static QString findClassName(const QJSManagedValue &value) +{ + if (value.isUndefined()) + return QStringLiteral("undefined"); + if (value.isBoolean()) + return QStringLiteral("boolean"); + if (value.isNumber()) + return QStringLiteral("number"); + if (value.isString()) + return QStringLiteral("string"); + if (value.isSymbol()) + return QStringLiteral("symbol"); + + QV4::Scope scope(value.engine()->handle()); + if (QV4::ScopedValue scoped(scope, asManaged(value)); scoped->isManaged()) + return scoped->managed()->vtable()->className; + + Q_UNREACHABLE_RETURN(QString()); +} + +static QString buildClass(const QJSManagedValue &value, QJsonArray *classes, + State *seen, const QString &name) +{ + if (value.isNull()) + return QString(); + + if (seen->primitives.contains(name)) + return name; + else if (name.at(0).isLower()) + seen->primitives.insert(name); + + QJsonObject classObject; + QV4::Scope scope(value.engine()->handle()); + + classObject[QStringLiteral("className")] = name; + classObject[QStringLiteral("qualifiedClassName")] = name; + + classObject[QStringLiteral("classInfos")] = QJsonArray({ + QJsonObject({ + { QStringLiteral("name"), QStringLiteral("QML.Element") }, + { QStringLiteral("value"), QStringLiteral("anonymous") } + }) + }); + + if (value.isObject() || value.isFunction()) + classObject[QStringLiteral("object")] = true; + else + classObject[QStringLiteral("gadget")] = true; + + const QJSManagedValue prototype = value.prototype(); + + if (!prototype.isNull()) { + QString protoName; + for (auto it = seen->prototypes.begin(), end = seen->prototypes.end(); it != end; ++it) { + if (prototype.strictlyEquals(QJSManagedValue(*it, value.engine()))) { + protoName = it.key(); + break; + } + } + + if (protoName.isEmpty()) { + if (name.endsWith(QStringLiteral("ErrorPrototype")) + && name != QStringLiteral("ErrorPrototype")) { + protoName = QStringLiteral("ErrorPrototype"); + } else if (name.endsWith(QStringLiteral("Prototype"))) { + protoName = findClassName(prototype); + if (!protoName.endsWith(QStringLiteral("Prototype"))) + protoName += QStringLiteral("Prototype"); + } else { + protoName = name.at(0).toUpper() + name.mid(1) + QStringLiteral("Prototype"); + } + + auto it = seen->prototypes.constFind(protoName); + if (it == seen->prototypes.cend()) { + seen->prototypes.insert(protoName, prototype.toJSValue()); + buildClass(prototype, classes, seen, protoName); + } else if (!it->strictlyEquals(prototype.toJSValue())) { + qWarning() << "Cannot find a distinct name for the prototype of" << name; + qWarning() << protoName << "is already in use."; + } + } + + classObject[QStringLiteral("superClasses")] = QJsonArray { + QJsonObject ({ + { QStringLiteral("access"), QStringLiteral("public") }, + { QStringLiteral("name"), protoName } + })}; + } + + QJsonArray properties, methods; + + auto defineProperty = [&](const QJSManagedValue &prop, const PropertyInfo &info) { + QJsonObject propertyObject; + propertyObject.insert(QStringLiteral("name"), info.name); + + // Insert faux member entry if we're allowed to write to this + if (info.writable) + propertyObject.insert(QStringLiteral("member"), QStringLiteral("fakeMember")); + + if (!prop.isUndefined() && !prop.isNull()) { + QString propClassName = findClassName(prop); + if (!propClassName.at(0).isLower() && info.name != QStringLiteral("prototype")) { + propClassName = (name == QStringLiteral("GlobalObject")) + ? QString() + : name.at(0).toUpper() + name.mid(1); + + propClassName += info.name.at(0).toUpper() + info.name.mid(1); + propertyObject.insert(QStringLiteral("type"), + buildClass(prop, classes, seen, propClassName)); + } else { + // If it's the "prototype" property we just refer to generic "Object", + // and if it's a value type, we handle it separately. + propertyObject.insert(QStringLiteral("type"), propClassName); + } + } + return propertyObject; + }; + + QList<PropertyInfo> unRetrievedProperties; + QJSManagedValue constructed; + for (const PropertyInfo &info : getPropertyInfos(value)) { + QJSManagedValue prop = checkedProperty(value, info.name); + if (prop.engine()->hasError()) { + unRetrievedProperties.append(info); + prop.engine()->catchError(); + continue; + } + + // Method or constructor + if (prop.isFunction()) { + QV4::Scoped<QV4::FunctionObject> propFunction(scope, asManaged(prop)); + + QJsonObject methodObject; + + methodObject.insert(QStringLiteral("access"), QStringLiteral("public")); + methodObject.insert(QStringLiteral("name"), info.name); + methodObject.insert(QStringLiteral("isJavaScriptFunction"), true); + + const int formalParams = propFunction->getLength(); + if (propFunction->isConstructor()) { + methodObject.insert(QStringLiteral("isConstructor"), true); + + QString ctorName; + if (info.name.at(0).isUpper()) { + ctorName = info.name; + } else if (info.name == QStringLiteral("constructor")) { + if (name.endsWith(QStringLiteral("Prototype"))) + ctorName = name.chopped(strlen("Prototype")); + else if (name.endsWith(QStringLiteral("PrototypeMember"))) + ctorName = name.chopped(strlen("PrototypeMember")); + else + ctorName = name; + + if (!ctorName.endsWith(QStringLiteral("Constructor"))) + ctorName += QStringLiteral("Constructor"); + } + + methodObject.insert( + QStringLiteral("returnType"), + buildConstructor(prop, classes, seen, ctorName, &constructed)); + } + + QJsonArray arguments; + for (int i = 0; i < formalParams; i++) + arguments.append(QJsonObject {}); + + methodObject.insert(QStringLiteral("arguments"), arguments); + + methods.append(methodObject); + + continue; + } + + // ...else it's just a property + properties.append(defineProperty(prop, info)); + } + + for (const PropertyInfo &info : unRetrievedProperties) { + QJSManagedValue prop = checkedProperty( + constructed.isUndefined() ? value : constructed, info.name); + if (prop.engine()->hasError()) { + qWarning() << "Cannot retrieve property " << info.name << "of" << name << constructed.toString(); + qWarning().noquote() << " " << prop.engine()->catchError().toString(); + } + + properties.append(defineProperty(prop, info)); + } + + classObject[QStringLiteral("properties")] = properties; + classObject[QStringLiteral("methods")] = methods; + + classes->append(classObject); + + return name; +} + +static QString buildConstructor(const QJSManagedValue &constructor, QJsonArray *classes, + State *seen, const QString &name, QJSManagedValue *constructed) +{ + QJSEngine *engine = constructor.engine(); + + // If the constructor appears in the global object, use the name from there. + const QJSManagedValue globalObject(engine->globalObject(), engine); + const auto infos = getPropertyInfos(globalObject); + for (const auto &info : infos) { + const QJSManagedValue member(globalObject.property(info.name), engine); + if (member.strictlyEquals(constructor) && info.name != name) + return buildConstructor(constructor, classes, seen, info.name, constructed); + } + + if (name == QStringLiteral("Symbol")) + return QStringLiteral("undefined"); // Cannot construct symbols with "new"; + + if (name == QStringLiteral("URL")) { + *constructed = QJSManagedValue( + constructor.callAsConstructor({ QJSValue(QStringLiteral("http://a.bc")) }), + engine); + } else if (name == QStringLiteral("Promise")) { + *constructed = QJSManagedValue( + constructor.callAsConstructor( + { engine->evaluate(QStringLiteral("(function() {})")) }), + engine); + } else if (name == QStringLiteral("DataView")) { + *constructed = QJSManagedValue( + constructor.callAsConstructor( + { engine->evaluate(QStringLiteral("new ArrayBuffer()")) }), + engine); + } else if (name == QStringLiteral("Proxy")) { + *constructed = QJSManagedValue(constructor.callAsConstructor( + { engine->newObject(), engine->newObject() }), engine); + } else { + *constructed = QJSManagedValue(constructor.callAsConstructor(), engine); + } + + if (engine->hasError()) { + qWarning() << "Calling constructor" << name << "failed"; + qWarning().noquote() << " " << engine->catchError().toString(); + return QString(); + } else if (name.isEmpty()) { + Q_UNREACHABLE(); + } + + auto it = seen->constructors.constFind(name); + if (it == seen->constructors.cend()) { + seen->constructors.insert(name, constructor.toJSValue()); + return buildClass(*constructed, classes, seen, name); + } else if (!constructor.strictlyEquals(QJSManagedValue(*it, constructor.engine()))) { + qWarning() << "Two constructors of the same name seen:" << name; + } + return name; +} + +int main(int argc, char *argv[]) +{ + QCoreApplication app(argc, argv); + QCoreApplication::setApplicationVersion(QLatin1String(QT_VERSION_STR)); + + QCommandLineParser parser; + parser.addHelpOption(); + parser.setApplicationDescription("Internal development tool."); + parser.addPositionalArgument("path", "Output json path.", "path"); + + parser.process(app); + + const QStringList args = parser.positionalArguments(); + if (auto size = args.size(); size == 0) { + qWarning().noquote().nospace() << app.applicationName() << ": Output path missing."; + return EXIT_FAILURE; + } else if (size >= 2) { + qWarning().noquote().nospace() << app.applicationName() << ": Too many output paths given. Only one allowed."; + } + + const QString fileName = args.at(0); + + QJSEngine engine; + engine.installExtensions(QJSEngine::AllExtensions); + + QJsonArray classesArray; + State seen; + + // object. Do this first to claim the "Object" name for the prototype. + buildClass(QJSManagedValue(engine.newObject(), &engine), &classesArray, &seen, + QStringLiteral("object")); + + + buildClass(QJSManagedValue(engine.globalObject(), &engine), &classesArray, &seen, + QStringLiteral("GlobalObject")); + + // Add JS types, in case they aren't used anywhere. + + + // function + buildClass(QJSManagedValue(engine.evaluate(QStringLiteral("(function() {})")), &engine), + &classesArray, &seen, QStringLiteral("function")); + + // string + buildClass(QJSManagedValue(QStringLiteral("s"), &engine), &classesArray, &seen, + QStringLiteral("string")); + + // undefined + buildClass(QJSManagedValue(QJSPrimitiveUndefined(), &engine), &classesArray, &seen, + QStringLiteral("undefined")); + + // number + buildClass(QJSManagedValue(QJSPrimitiveValue(1.1), &engine), &classesArray, &seen, + QStringLiteral("number")); + + // boolean + buildClass(QJSManagedValue(QJSPrimitiveValue(true), &engine), &classesArray, &seen, + QStringLiteral("boolean")); + + // symbol + buildClass(QJSManagedValue(engine.newSymbol(QStringLiteral("s")), &engine), + &classesArray, &seen, QStringLiteral("symbol")); + + // Generate the fake metatypes json structure + QJsonDocument metatypesJson = QJsonDocument( + QJsonArray({ + QJsonObject({ + {QStringLiteral("classes"), classesArray} + }) + }) + ); + + QFile file(fileName); + if (!file.open(QFile::WriteOnly)) { + qWarning() << "Failed to write metatypes json to" << fileName; + return 1; + } + + file.write(metatypesJson.toJson()); + file.close(); + + return 0; +} diff --git a/tools/qmllint/.prev_CMakeLists.txt b/tools/qmllint/.prev_CMakeLists.txt deleted file mode 100644 index 3b5a152f4b..0000000000 --- a/tools/qmllint/.prev_CMakeLists.txt +++ /dev/null @@ -1,23 +0,0 @@ -# Generated from qmllint.pro. - -##################################################################### -## qmllint Tool: -##################################################################### - -qt_get_tool_target_name(target_name qmllint) -qt_internal_add_tool(${target_name} - TARGET_DESCRIPTION "QML Syntax Verifier" - SOURCES - checkidentifiers.cpp checkidentifiers.h - findwarnings.cpp findwarnings.h - main.cpp - qcoloroutput.cpp qcoloroutput.h - PUBLIC_LIBRARIES - Qt::CorePrivate - Qt::QmlCompilerPrivate - Qt::QmlDevToolsPrivate -) - -#### Keys ignored in scope 1:.:.:qmllint.pro:<TRUE>: -# QMAKE_TARGET_DESCRIPTION = "QML" "Syntax" "Verifier" -# _OPTION = "host_build" diff --git a/tools/qmllint/CMakeLists.txt b/tools/qmllint/CMakeLists.txt index a133af3cb7..ae3de5901d 100644 --- a/tools/qmllint/CMakeLists.txt +++ b/tools/qmllint/CMakeLists.txt @@ -1,3 +1,6 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + # Generated from qmllint.pro. ##################################################################### @@ -9,15 +12,13 @@ qt_internal_add_tool(${target_name} TARGET_DESCRIPTION "QML Syntax Verifier" TOOLS_TARGET Qml # special case SOURCES - checkidentifiers.cpp checkidentifiers.h - findwarnings.cpp findwarnings.h main.cpp - qcoloroutput.cpp qcoloroutput.h - PUBLIC_LIBRARIES + LIBRARIES Qt::CorePrivate Qt::QmlCompilerPrivate - Qt::QmlDevToolsPrivate + Qt::QmlToolingSettingsPrivate ) +qt_internal_return_unless_building_tools() #### Keys ignored in scope 1:.:.:qmllint.pro:<TRUE>: # QMAKE_TARGET_DESCRIPTION = "QML" "Syntax" "Verifier" diff --git a/tools/qmllint/checkidentifiers.cpp b/tools/qmllint/checkidentifiers.cpp deleted file mode 100644 index b0c56bcec6..0000000000 --- a/tools/qmllint/checkidentifiers.cpp +++ /dev/null @@ -1,465 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2020 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 "checkidentifiers.h" -#include "qcoloroutput.h" - -#include <QtCore/qqueue.h> -#include <QtCore/qsharedpointer.h> -#include <stack> - -class IssueLocationWithContext -{ -public: - IssueLocationWithContext(const QString &code, const QQmlJS::SourceLocation &location) { - int before = qMax(0,code.lastIndexOf(QLatin1Char('\n'), location.offset)); - m_beforeText = QStringView{code}.mid(before + 1, int(location.offset - (before + 1))); - m_issueText = QStringView{code}.mid(location.offset, location.length); - int after = code.indexOf(QLatin1Char('\n'), int(location.offset + location.length)); - m_afterText = QStringView{code}.mid(int(location.offset + location.length), - int(after - (location.offset+location.length))); - } - - QStringView beforeText() const { return m_beforeText; } - QStringView issueText() const { return m_issueText; } - QStringView afterText() const { return m_afterText; } - -private: - QStringView m_beforeText; - QStringView m_issueText; - QStringView m_afterText; -}; - -static const QStringList unknownBuiltins = { - QStringLiteral("alias"), // TODO: we cannot properly resolve aliases, yet - QStringLiteral("QJSValue"), // We cannot say anything intelligent about untyped JS values. - - // Same for generic variants - QStringLiteral("variant"), - QStringLiteral("var") -}; - -void CheckIdentifiers::printContext( - const QString &code, ColorOutput *output, const QQmlJS::SourceLocation &location) -{ - IssueLocationWithContext issueLocationWithContext { code, location }; - output->write(issueLocationWithContext.beforeText().toString(), Normal); - output->write(issueLocationWithContext.issueText().toString(), Error); - output->write(issueLocationWithContext.afterText().toString() + QLatin1Char('\n'), Normal); - int tabCount = issueLocationWithContext.beforeText().count(QLatin1Char('\t')); - output->write(QString::fromLatin1(" ").repeated( - issueLocationWithContext.beforeText().length() - tabCount) - + QString::fromLatin1("\t").repeated(tabCount) - + QString::fromLatin1("^").repeated(location.length) - + QLatin1Char('\n'), Normal); -} - -template<typename Visitor> -static bool walkRelatedScopes(QQmlJSScope::ConstPtr rootType, const Visitor &visit) -{ - if (rootType.isNull()) - return false; - std::stack<QQmlJSScope::ConstPtr> stack; - stack.push(rootType); - - while (!stack.empty()) { - const auto type = stack.top(); - stack.pop(); - - if (visit(type)) - return true; - - if (auto attachedType = type->attachedType()) - stack.push(attachedType); - - if (auto baseType = type->baseType()) - stack.push(baseType); - - // Push extension type last. It overrides the base type. - if (auto extensionType = type->extensionType()) - stack.push(extensionType); - } - - return false; -} - -bool CheckIdentifiers::checkMemberAccess(const QVector<FieldMember> &members, - const QQmlJSScope::ConstPtr &outerScope, - const QQmlJSMetaProperty *prop) const -{ - - QStringList expectedNext; - QString detectedRestrictiveName; - QString detectedRestrictiveKind; - - if (prop != nullptr && prop->isList()) { - detectedRestrictiveKind = QLatin1String("list"); - expectedNext.append(QLatin1String("length")); - } - - QQmlJSScope::ConstPtr scope = outerScope; - for (const FieldMember &access : members) { - if (scope.isNull()) { - m_colorOut->writePrefixedMessage( - QString::fromLatin1("Type \"%1\" of base \"%2\" not found when accessing member \"%3\" at %4:%5:%6.\n") - .arg(detectedRestrictiveKind) - .arg(detectedRestrictiveName) - .arg(access.m_name) - .arg(m_fileName) - .arg(access.m_location.startLine) - .arg(access.m_location.startColumn), Warning); - printContext(m_code, m_colorOut, access.m_location); - return false; - } - - if (!detectedRestrictiveKind.isEmpty()) { - if (expectedNext.contains(access.m_name)) { - expectedNext.clear(); - continue; - } - - m_colorOut->writePrefixedMessage(QString::fromLatin1( - "\"%1\" is a %2. You cannot access \"%3\" on it at %4:%5:%6\n") - .arg(detectedRestrictiveName) - .arg(detectedRestrictiveKind) - .arg(access.m_name) - .arg(m_fileName) - .arg(access.m_location.startLine) - .arg(access.m_location.startColumn), Warning); - printContext(m_code, m_colorOut, access.m_location); - return false; - } - - const auto property = scope->property(access.m_name); - if (!property.propertyName().isEmpty()) { - const QString typeName = access.m_parentType.isEmpty() ? property.typeName() - : access.m_parentType; - if (property.isList()) { - detectedRestrictiveKind = QLatin1String("list"); - detectedRestrictiveName = access.m_name; - expectedNext.append(QLatin1String("length")); - continue; - } - - if (typeName == QLatin1String("string")) { - detectedRestrictiveKind = typeName; - detectedRestrictiveName = access.m_name; - expectedNext.append(QLatin1String("length")); - continue; - } - - if (access.m_parentType.isEmpty()) { - scope = property.type(); - if (scope.isNull()) { - // Properties should always have a type. Otherwise something - // was missing from the import already. - detectedRestrictiveKind = typeName; - detectedRestrictiveName = access.m_name; - } - continue; - } - - if (unknownBuiltins.contains(typeName)) - return true; - - const auto it = m_types.find(typeName); - if (it == m_types.end()) { - detectedRestrictiveKind = typeName; - detectedRestrictiveName = access.m_name; - scope = QQmlJSScope::ConstPtr(); - } else { - scope = *it; - } - - continue; - } - - if (scope->hasMethod(access.m_name)) - return true; // Access to property of JS function - - auto checkEnums = [&](const QQmlJSScope::ConstPtr &scope) { - if (scope->hasEnumeration(access.m_name)) { - detectedRestrictiveKind = QLatin1String("enum"); - detectedRestrictiveName = access.m_name; - expectedNext.append(scope->enumeration(access.m_name).keys()); - return true; - } - - if (scope->hasEnumerationKey(access.m_name)) { - detectedRestrictiveKind = QLatin1String("enum"); - detectedRestrictiveName = access.m_name; - return true; - } - - return false; - }; - - checkEnums(scope); - - if (!detectedRestrictiveName.isEmpty()) - continue; - - QQmlJSScope::ConstPtr rootType; - if (!access.m_parentType.isEmpty()) - rootType = m_types.value(access.m_parentType); - else - rootType = scope; - - bool typeFound = - walkRelatedScopes(rootType, [&](QQmlJSScope::ConstPtr type) { - const auto typeProperties = type->ownProperties(); - const auto typeIt = typeProperties.find(access.m_name); - if (typeIt != typeProperties.end()) { - scope = typeIt->type(); - return true; - } - - const auto typeMethods = type->ownMethods(); - const auto typeMethodIt = typeMethods.find(access.m_name); - if (typeMethodIt != typeMethods.end()) { - detectedRestrictiveName = access.m_name; - detectedRestrictiveKind = QLatin1String("method"); - return true; - } - - return checkEnums(type); - }); - if (typeFound) - continue; - - if (access.m_name.front().isUpper() && scope->scopeType() == QQmlJSScope::QMLScope) { - // may be an attached type - const auto it = m_types.find(access.m_name); - if (it != m_types.end() && *it && !(*it)->attachedTypeName().isEmpty()) { - if (const auto attached = (*it)->attachedType()) { - scope = attached; - continue; - } - } - } - - m_colorOut->writePrefixedMessage(QString::fromLatin1( - "Property \"%1\" not found on type \"%2\" at %3:%4:%5\n") - .arg(access.m_name) - .arg(scope->internalName().isEmpty() - ? scope->baseTypeName() : scope->internalName()) - .arg(m_fileName) - .arg(access.m_location.startLine) - .arg(access.m_location.startColumn), Warning); - printContext(m_code, m_colorOut, access.m_location); - return false; - } - - return true; -} - -bool CheckIdentifiers::operator()( - const QHash<QString, QQmlJSScope::ConstPtr> &qmlIDs, - const QHash<QQmlJS::SourceLocation, SignalHandler> &signalHandlers, - const MemberAccessChains &memberAccessChains, - const QQmlJSScope::ConstPtr &root, const QString &rootId) const -{ - bool identifiersClean = true; - - // revisit all scopes - QQueue<QQmlJSScope::ConstPtr> workQueue; - workQueue.enqueue(root); - while (!workQueue.empty()) { - const QQmlJSScope::ConstPtr currentScope = workQueue.dequeue(); - - const auto scopeMemberAccessChains = memberAccessChains[currentScope]; - for (auto memberAccessChain : scopeMemberAccessChains) { - if (memberAccessChain.isEmpty()) - continue; - - auto memberAccessBase = memberAccessChain.takeFirst(); - const auto jsId = currentScope->findJSIdentifier(memberAccessBase.m_name); - if (jsId.has_value() && jsId->kind != QQmlJSScope::JavaScriptIdentifier::Injected) { - if (memberAccessBase.m_location.end() < jsId->location.begin()) { - m_colorOut->writePrefixedMessage( - QStringLiteral( - "Variable \"%1\" is used before its declaration at %2:%3. " - "The declaration is at %4:%5.\n") - .arg(memberAccessBase.m_name) - .arg(memberAccessBase.m_location.startLine) - .arg(memberAccessBase.m_location.startColumn) - .arg(jsId->location.startLine) - .arg(jsId->location.startColumn), Warning); - printContext(m_code, m_colorOut, memberAccessBase.m_location); - identifiersClean = false; - } - continue; - } - - auto it = qmlIDs.find(memberAccessBase.m_name); - if (it != qmlIDs.end()) { - if (!it->isNull()) { - if (!checkMemberAccess(memberAccessChain, *it)) - identifiersClean = false; - continue; - } else if (!memberAccessChain.isEmpty()) { - // It could be a qualified type name - const QString scopedName = memberAccessChain.first().m_name; - if (scopedName.front().isUpper()) { - const QString qualified = memberAccessBase.m_name + QLatin1Char('.') - + scopedName; - const auto typeIt = m_types.find(qualified); - if (typeIt != m_types.end()) { - memberAccessChain.takeFirst(); - if (!checkMemberAccess(memberAccessChain, *typeIt)) - identifiersClean = false; - continue; - } - } - } - } - - auto qmlScope = QQmlJSScope::findCurrentQMLScope(currentScope); - if (qmlScope->hasMethod(memberAccessBase.m_name)) { - // a property of a JavaScript function, or a method - continue; - } - - const auto property = qmlScope->property(memberAccessBase.m_name); - if (!property.propertyName().isEmpty()) { - if (memberAccessChain.isEmpty() || unknownBuiltins.contains(property.typeName())) - continue; - - if (!property.type()) { - m_colorOut->writePrefixedMessage(QString::fromLatin1( - "Type of property \"%2\" not found at %3:%4:%5\n") - .arg(memberAccessBase.m_name) - .arg(m_fileName) - .arg(memberAccessBase.m_location.startLine) - .arg(memberAccessBase.m_location.startColumn), Warning); - printContext(m_code, m_colorOut, memberAccessBase.m_location); - identifiersClean = false; - } else if (!checkMemberAccess(memberAccessChain, property.type(), &property)) { - identifiersClean = false; - } - - continue; - } - - const QString baseName = memberAccessBase.m_name; - auto typeIt = m_types.find(memberAccessBase.m_name); - bool baseIsPrefixed = false; - while (typeIt != m_types.end() && typeIt->isNull()) { - // This is a namespaced import. Check with the full name. - if (!memberAccessChain.isEmpty()) { - auto location = memberAccessBase.m_location; - memberAccessBase = memberAccessChain.takeFirst(); - memberAccessBase.m_name.prepend(baseName + u'.'); - location.length = memberAccessBase.m_location.offset - location.offset - + memberAccessBase.m_location.length; - memberAccessBase.m_location = location; - typeIt = m_types.find(memberAccessBase.m_name); - baseIsPrefixed = true; - } - } - - if (typeIt != m_types.end() && !typeIt->isNull()) { - if (!checkMemberAccess(memberAccessChain, *typeIt)) - identifiersClean = false; - continue; - } - - identifiersClean = false; - const auto location = memberAccessBase.m_location; - - if (baseIsPrefixed) { - m_colorOut->writePrefixedMessage( - QString::fromLatin1("type not found in namespace at %1:%2:%3\n") - .arg(m_fileName) - .arg(location.startLine).arg(location.startColumn), - Warning); - } else { - m_colorOut->writePrefixedMessage( - QString::fromLatin1("unqualified access at %1:%2:%3\n") - .arg(m_fileName) - .arg(location.startLine).arg(location.startColumn), - Warning); - } - - printContext(m_code, m_colorOut, location); - - // root(JS) --> (first element) - const auto firstElement = root->childScopes()[0]; - if (firstElement->hasProperty(memberAccessBase.m_name) - || firstElement->hasMethod(memberAccessBase.m_name) - || firstElement->hasEnumeration(memberAccessBase.m_name)) { - m_colorOut->writePrefixedMessage( - memberAccessBase.m_name - + QLatin1String(" is a member of the root element\n") - + QLatin1String(" You can qualify the access with its id " - "to avoid this warning:\n"), - Info, QStringLiteral("Note")); - if (rootId == QLatin1String("<id>")) { - m_colorOut->writePrefixedMessage( - QLatin1String("You first have to give the root element an id\n"), - Warning, QStringLiteral("Note")); - } - IssueLocationWithContext issueLocationWithContext {m_code, location}; - m_colorOut->write(issueLocationWithContext.beforeText().toString(), Normal); - m_colorOut->write(rootId + QLatin1Char('.'), Hint); - m_colorOut->write(issueLocationWithContext.issueText().toString(), Normal); - m_colorOut->write(issueLocationWithContext.afterText() + QLatin1Char('\n'), Normal); - } else if (jsId.has_value() - && jsId->kind == QQmlJSScope::JavaScriptIdentifier::Injected) { - const QQmlJSScope::JavaScriptIdentifier id = jsId.value(); - m_colorOut->writePrefixedMessage( - memberAccessBase.m_name + QString::fromLatin1( - " is accessible in this scope because " - "you are handling a signal at %1:%2:%3\n") - .arg(m_fileName) - .arg(id.location.startLine).arg(id.location.startColumn), - Info, QStringLiteral("Note")); - m_colorOut->write(QLatin1String("Consider using a function instead\n"), Normal); - IssueLocationWithContext context {m_code, id.location}; - m_colorOut->write(context.beforeText() + QLatin1Char(' ')); - - const auto handler = signalHandlers[id.location]; - - m_colorOut->write(QLatin1String(handler.isMultiline ? "function(" : "("), Hint); - const auto parameters = handler.signal.parameterNames(); - for (int numParams = parameters.size(); numParams > 0; --numParams) { - m_colorOut->write(parameters.at(parameters.size() - numParams), Hint); - if (numParams > 1) - m_colorOut->write(QLatin1String(", "), Hint); - } - m_colorOut->write(QLatin1String(handler.isMultiline ? ")" : ") => "), Hint); - m_colorOut->write(QLatin1String(" {..."), Normal); - } - m_colorOut->write(QLatin1String("\n\n\n"), Normal); - } - const auto childScopes = currentScope->childScopes(); - for (auto const &childScope : childScopes) - workQueue.enqueue(childScope); - } - return identifiersClean; -} diff --git a/tools/qmllint/checkidentifiers.h b/tools/qmllint/checkidentifiers.h deleted file mode 100644 index f4fd8c9f14..0000000000 --- a/tools/qmllint/checkidentifiers.h +++ /dev/null @@ -1,78 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2020 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 CHECKIDENTIFIERS_H -#define CHECKIDENTIFIERS_H - -#include <QtQmlCompiler/private/qqmljsscope_p.h> -#include <QtQmlCompiler/private/qqmljsimporter_p.h> - -class ColorOutput; - -struct SignalHandler { - QQmlJSMetaMethod signal; - bool isMultiline; -}; - -struct FieldMember -{ - QString m_name; - QString m_parentType; - QQmlJS::SourceLocation m_location; -}; - -using MemberAccessChains = QHash<QQmlJSScope::ConstPtr, QVector<QVector<FieldMember>>>; - -class CheckIdentifiers -{ -public: - CheckIdentifiers(ColorOutput *colorOut, const QString &code, - const QQmlJSImporter::ImportedTypes &types, const QString &fileName) : - m_colorOut(colorOut), m_code(code), m_types(types), m_fileName(fileName) - {} - - bool operator ()(const QHash<QString, QQmlJSScope::ConstPtr> &qmlIDs, - const QHash<QQmlJS::SourceLocation, SignalHandler> &signalHandlers, - const MemberAccessChains &memberAccessChains, - const QQmlJSScope::ConstPtr &root, const QString &rootId) const; - - static void printContext(const QString &code, ColorOutput *output, - const QQmlJS::SourceLocation &location); - -private: - bool checkMemberAccess(const QVector<FieldMember> &members, - const QQmlJSScope::ConstPtr &outerScope, - const QQmlJSMetaProperty *prop = nullptr) const; - - ColorOutput *m_colorOut = nullptr; - QString m_code; - QQmlJSImporter::ImportedTypes m_types; - QString m_fileName; -}; - -#endif // CHECKIDENTIFIERS_H diff --git a/tools/qmllint/findwarnings.cpp b/tools/qmllint/findwarnings.cpp deleted file mode 100644 index e0e1c3831e..0000000000 --- a/tools/qmllint/findwarnings.cpp +++ /dev/null @@ -1,591 +0,0 @@ -/**************************************************************************** -** -** 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 "findwarnings.h" -#include "checkidentifiers.h" - -#include <QtQmlCompiler/private/qqmljsscope_p.h> -#include <QtQmlCompiler/private/qqmljstypedescriptionreader_p.h> -#include <QtQmlCompiler/private/qqmljstypereader_p.h> - -#include <QtQml/private/qqmljsast_p.h> -#include <QtQml/private/qqmljslexer_p.h> -#include <QtQml/private/qqmljsparser_p.h> -#include <QtQml/private/qv4codegen_p.h> -#include <QtQml/private/qqmlimportresolver_p.h> - -#include <QtCore/qfile.h> -#include <QtCore/qdiriterator.h> -#include <QtCore/qscopedvaluerollback.h> - -void FindWarningVisitor::checkInheritanceCycle(QQmlJSScope::ConstPtr scope) -{ - QList<QQmlJSScope::ConstPtr> scopes; - while (!scope.isNull()) { - if (scopes.contains(scope)) { - QString inheritenceCycle; - for (const auto &seen: qAsConst(scopes)) { - if (!inheritenceCycle.isEmpty()) - inheritenceCycle.append(QLatin1String(" -> ")); - inheritenceCycle.append(seen->baseTypeName()); - } - - if (m_warnInheritanceCycle) { - m_errors.append({ - QStringLiteral("%1 is part of an inheritance cycle: %2\n") - .arg(scope->internalName()) - .arg(inheritenceCycle), - QtWarningMsg, - QQmlJS::SourceLocation() - }); - } - - m_unknownImports.insert(scope->internalName()); - m_visitFailed = true; - break; - } - - scopes.append(scope); - - if (scope->baseTypeName().isEmpty()) { - break; - } else if (auto newScope = scope->baseType()) { - scope = newScope; - } else { - m_errors.append({ - scope->baseTypeName() + QStringLiteral( - " was not found. Did you add all import paths?\n"), - QtWarningMsg, - QQmlJS::SourceLocation() - }); - m_unknownImports.insert(scope->baseTypeName()); - m_visitFailed = true; - break; - } - } -} - -void FindWarningVisitor::checkGroupedAndAttachedScopes(QQmlJSScope::ConstPtr scope) -{ - auto children = scope->childScopes(); - while (!children.isEmpty()) { - auto childScope = children.takeFirst(); - const auto type = childScope->scopeType(); - switch (type) { - case QQmlJSScope::GroupedPropertyScope: - case QQmlJSScope::AttachedPropertyScope: - if (!childScope->baseType()) { - m_errors.append({ - QStringLiteral("unknown %1 property scope %2.") - .arg(type == QQmlJSScope::GroupedPropertyScope - ? QStringLiteral("grouped") - : QStringLiteral("attached"), - childScope->internalName()), - QtWarningMsg, - childScope->sourceLocation() - }); - m_visitFailed = true; - } - children.append(childScope->childScopes()); - default: - break; - } - } -} - -void FindWarningVisitor::flushPendingSignalParameters() -{ - const SignalHandler handler = m_signalHandlers[m_pendingSingalHandler]; - for (const QString ¶meter : handler.signal.parameterNames()) { - m_currentScope->insertJSIdentifier( - parameter, { - QQmlJSScope::JavaScriptIdentifier::Injected, - m_pendingSingalHandler - }); - } - m_pendingSingalHandler = QQmlJS::SourceLocation(); -} - -void FindWarningVisitor::throwRecursionDepthError() -{ - QQmlJSImportVisitor::throwRecursionDepthError(); - m_visitFailed = true; -} - -bool FindWarningVisitor::visit(QQmlJS::AST::ExpressionStatement *ast) -{ - if (m_pendingSingalHandler.isValid()) { - enterEnvironment(QQmlJSScope::JSFunctionScope, "signalhandler", ast->firstSourceLocation()); - flushPendingSignalParameters(); - } - return true; -} - -void FindWarningVisitor::endVisit(QQmlJS::AST::ExpressionStatement *) -{ - if (m_currentScope->scopeType() == QQmlJSScope::JSFunctionScope - && m_currentScope->baseTypeName() == "signalhandler") { - leaveEnvironment(); - } -} - -bool FindWarningVisitor::visit(QQmlJS::AST::Block *block) -{ - if (!QQmlJSImportVisitor::visit(block)) - return false; - if (m_pendingSingalHandler.isValid()) - flushPendingSignalParameters(); - return true; -} - -bool FindWarningVisitor::visit(QQmlJS::AST::WithStatement *withStatement) -{ - if (m_warnWithStatement) { - m_errors.append({ - QStringLiteral( - "with statements are strongly discouraged in QML " - "and might cause false positives when analysing unqualified " - "identifiers\n"), - QtWarningMsg, - withStatement->firstSourceLocation() - }); - } - - return QQmlJSImportVisitor::visit(withStatement); -} - -static QString signalName(QStringView handlerName) -{ - if (handlerName.startsWith(u"on") && handlerName.size() > 2) { - QString signal = handlerName.mid(2).toString(); - for (int i = 0; i < signal.length(); ++i) { - QChar &ch = signal[i]; - if (ch.isLower()) - return QString(); - if (ch.isUpper()) { - ch = ch.toLower(); - return signal; - } - } - } - return QString(); -} - -bool FindWarningVisitor::visit(QQmlJS::AST::UiScriptBinding *uisb) -{ - using namespace QQmlJS::AST; - - const auto qmlScope = m_currentScope; - if (!QQmlJSImportVisitor::visit(uisb)) - return false; - - auto name = uisb->qualifiedId->name; - if (name == QLatin1String("id")) { - // Figure out whether the current scope is the root scope. - if (auto parentScope = qmlScope->parentScope()) { - if (!parentScope->parentScope()) { - const auto expstat = cast<ExpressionStatement *>(uisb->statement); - const auto identexp = cast<IdentifierExpression *>(expstat->expression); - m_rootId = identexp->name.toString(); - } - } - return true; - } - - const QString signal = signalName(name); - if (signal.isEmpty()) { - for (const auto &childScope : qmlScope->childScopes()) { - if ((childScope->scopeType() == QQmlJSScope::AttachedPropertyScope - || childScope->scopeType() == QQmlJSScope::GroupedPropertyScope) - && childScope->internalName() == name) { - return true; - } - } - - if (!qmlScope->hasProperty(name.toString())) { - m_errors.append({ - QStringLiteral("Binding assigned to \"%1\", but no property \"%1\" " - "exists in the current element.\n").arg(name), - QtWarningMsg, - uisb->firstSourceLocation() - }); - m_visitFailed = true; - return true; - } - - const auto property = qmlScope->property(name.toString()); - if (!property.type()) { - m_errors.append({ - QStringLiteral("No type found for property \"%1\". This may be due " - "to a missing import statement or incomplete " - "qmltypes files.\n").arg(name), - QtWarningMsg, - uisb->firstSourceLocation() - }); - m_visitFailed = true; - - } - - return true; - } - - - if (!qmlScope->hasMethod(signal) && m_warnUnqualified) { - m_errors.append({ - QStringLiteral("no matching signal found for handler \"%1\"\n") - .arg(name.toString()), - QtWarningMsg, - uisb->firstSourceLocation() - }); - m_visitFailed = true; - return true; - } - - QQmlJSMetaMethod scopeSignal; - for (QQmlJSScope::ConstPtr scope = qmlScope; scope; scope = scope->baseType()) { - const auto methods = scope->ownMethods(); - const auto methodsRange = methods.equal_range(signal); - for (auto method = methodsRange.first; method != methodsRange.second; ++method) { - if (method->methodType() != QQmlJSMetaMethod::Signal) - continue; - scopeSignal = *method; - break; - } - } - - const auto statement = uisb->statement; - if (ExpressionStatement *expr = cast<ExpressionStatement *>(statement)) { - if (FunctionExpression *func = expr->expression->asFunctionDefinition()) { - // functions are already handled - // they do not get names inserted according to the signal, but access their formal - // parameters. Let's still check if the names match, though. - const QStringList signalParameters = scopeSignal.parameterNames(); - qsizetype i = 0, end = signalParameters.length(); - for (FormalParameterList *formal = func->formals; - formal; ++i, formal = formal->next) { - if (i == end) { - m_errors.append({ - QStringLiteral("Signal handler for \"%2\" has more formal" - " parameters than the signal it handles.") - .arg(name), - QtWarningMsg, - uisb->firstSourceLocation() - }); - m_visitFailed = true; - } - - const QStringView handlerParameter = formal->element->bindingIdentifier; - const qsizetype j = signalParameters.indexOf(handlerParameter); - if (j == i || j < 0) - continue; - - m_errors.append({ - QStringLiteral("Parameter %1 to signal handler for \"%2\"" - " is called \"%3\". The signal has a parameter" - " of the same name in position %4.\n") - .arg(i + 1).arg(name, handlerParameter).arg(j + 1), - QtWarningMsg, - uisb->firstSourceLocation() - }); - m_visitFailed = true; - } - - return true; - } - } - - const auto firstSourceLocation = statement->firstSourceLocation(); - bool hasMultilineStatementBody - = statement->lastSourceLocation().startLine > firstSourceLocation.startLine; - m_pendingSingalHandler = firstSourceLocation; - m_signalHandlers.insert(firstSourceLocation, {scopeSignal, hasMultilineStatementBody}); - return true; -} - -bool FindWarningVisitor::visit(QQmlJS::AST::IdentifierExpression *idexp) -{ - m_memberAccessChains[m_currentScope].append( - {{idexp->name.toString(), QString(), idexp->firstSourceLocation()}}); - m_fieldMemberBase = idexp; - return true; -} - -FindWarningVisitor::FindWarningVisitor( - QQmlJSImporter *importer, QStringList qmltypesFiles, QString code, QString fileName, - bool silent, bool warnUnqualified, bool warnWithStatement, bool warnInheritanceCycle) - : QQmlJSImportVisitor(importer, - implicitImportDirectory(fileName, importer->resourceFileMapper()), - qmltypesFiles), - m_code(std::move(code)), - m_rootId(QLatin1String("<id>")), - m_filePath(std::move(fileName)), - m_colorOut(silent), - m_warnUnqualified(warnUnqualified), - m_warnWithStatement(warnWithStatement), - m_warnInheritanceCycle(warnInheritanceCycle) -{ - m_currentScope->setInternalName("global"); - - // setup color output - m_colorOut.insertMapping(Error, ColorOutput::RedForeground); - m_colorOut.insertMapping(Warning, ColorOutput::PurpleForeground); - m_colorOut.insertMapping(Info, ColorOutput::BlueForeground); - m_colorOut.insertMapping(Normal, ColorOutput::DefaultColor); - m_colorOut.insertMapping(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") - }; - - QQmlJSScope::JavaScriptIdentifier globalJavaScript = { - QQmlJSScope::JavaScriptIdentifier::LexicalScoped, - QQmlJS::SourceLocation() - }; - for (const char **globalName = QV4::Compiler::Codegen::s_globalNames; - *globalName != nullptr; - ++globalName) { - m_currentScope->insertJSIdentifier(QString::fromLatin1(*globalName), globalJavaScript); - } - for (const auto& jsGlobVar: jsGlobVars) - m_currentScope->insertJSIdentifier(jsGlobVar, globalJavaScript); -} - -static MessageColors messageColor(QtMsgType type) -{ - switch (type) { - case QtDebugMsg: - return Normal; - case QtWarningMsg: - return Warning; - case QtCriticalMsg: - case QtFatalMsg: - return Error; - case QtInfoMsg: - return Info; - } - - return Normal; -} - -bool FindWarningVisitor::check() -{ - for (const auto &error : qAsConst(m_errors)) { - if (error.loc.isValid()) { - m_colorOut.writePrefixedMessage( - QStringLiteral("%1:%2: %3") - .arg(error.loc.startLine).arg(error.loc.startColumn).arg(error.message), - messageColor(error.type)); - } else { - m_colorOut.writePrefixedMessage(error.message, messageColor(error.type)); - } - } - - // now that all ids are known, revisit any Connections whose target were perviously unknown - for (auto const &outstandingConnection: m_outstandingConnections) { - auto targetScope = m_scopesById[outstandingConnection.targetName]; - if (outstandingConnection.scope) { - for (const auto scope = targetScope; targetScope; - targetScope = targetScope->baseType()) { - const auto connectionMethods = targetScope->ownMethods(); - for (const auto &method : connectionMethods) - outstandingConnection.scope->addOwnMethod(method); - } - } - QScopedValueRollback<QQmlJSScope::Ptr> rollback(m_currentScope, outstandingConnection.scope); - outstandingConnection.uiod->initializer->accept(this); - } - - if (!m_warnUnqualified) - return true; - - CheckIdentifiers check(&m_colorOut, m_code, m_rootScopeImports, m_filePath); - return check(m_scopesById, m_signalHandlers, m_memberAccessChains, m_globalScope, m_rootId) - && !m_visitFailed; -} - -bool FindWarningVisitor::visit(QQmlJS::AST::UiObjectBinding *uiob) -{ - if (!QQmlJSImportVisitor::visit(uiob)) - return false; - - checkInheritanceCycle(m_currentScope); - return true; -} - -bool FindWarningVisitor::visit(QQmlJS::AST::UiObjectDefinition *uiod) -{ - using namespace QQmlJS::AST; - - if (!QQmlJSImportVisitor::visit(uiod)) - return false; - - const QString name = m_currentScope->baseTypeName(); - if (name.isEmpty() || name.front().isLower()) - return false; // Ignore grouped properties for now - - checkInheritanceCycle(m_currentScope); - - 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; - } - QQmlJSScope::ConstPtr targetScope; - if (target.isEmpty()) { - // no target set, connection comes from parentF - QQmlJSScope::Ptr scope = m_currentScope; - do { - scope = scope->parentScope(); // TODO: rename method - } while (scope->scopeType() != QQmlJSScope::QMLScope); - targetScope = m_rootScopeImports.value(scope->baseTypeName()); - } else { - // there was a target, check if we already can find it - auto scopeIt = m_scopesById.find(target); - if (scopeIt != m_scopesById.end()) { - targetScope = *scopeIt; - } else { - m_outstandingConnections.push_back({target, m_currentScope, uiod}); - return false; // visit children later once target is known - } - } - for (const auto scope = targetScope; targetScope; targetScope = targetScope->baseType()) { - const auto connectionMethods = targetScope->ownMethods(); - for (const auto &method : connectionMethods) - m_currentScope->addOwnMethod(method); - } - } - return true; -} - -bool FindWarningVisitor::visit(QQmlJS::AST::PatternElement *element) -{ - if (element->isVariableDeclaration()) { - QQmlJS::AST::BoundNames names; - element->boundNames(&names); - for (const auto &name : names) { - m_currentScope->insertJSIdentifier( - name.id, { - (element->scope == QQmlJS::AST::VariableScope::Var) - ? QQmlJSScope::JavaScriptIdentifier::FunctionScoped - : QQmlJSScope::JavaScriptIdentifier::LexicalScoped, - element->firstSourceLocation() - }); - } - } - - return true; -} - -void FindWarningVisitor::endVisit(QQmlJS::AST::UiObjectDefinition *uiod) -{ - auto childScope = m_currentScope; - QQmlJSImportVisitor::endVisit(uiod); - - if (m_warnUnqualified) - checkGroupedAndAttachedScopes(childScope); - - if (m_currentScope == m_globalScope - || m_currentScope->baseTypeName() == QStringLiteral("Component")) { - return; - } - - auto property = childScope->property(QStringLiteral("parent")); - if (!property.propertyName().isEmpty()) { - property.setType(QQmlJSScope::ConstPtr(m_currentScope)); - childScope->addOwnProperty(property); - } -} - -bool FindWarningVisitor::visit(QQmlJS::AST::FieldMemberExpression *) -{ - return true; -} - -void FindWarningVisitor::endVisit(QQmlJS::AST::FieldMemberExpression *fieldMember) -{ - using namespace QQmlJS::AST; - ExpressionNode *base = fieldMember->base; - while (auto *nested = cast<NestedExpression *>(base)) - base = nested->expression; - - if (m_fieldMemberBase == base) { - QString type; - if (auto *binary = cast<BinaryExpression *>(base)) { - if (binary->op == QSOperator::As) { - if (auto *right = cast<TypeExpression *>(binary->right)) - type = right->m_type->toString(); - } - } - - - auto &chain = m_memberAccessChains[m_currentScope]; - Q_ASSERT(!chain.last().isEmpty()); - chain.last().append(FieldMember { - fieldMember->name.toString(), type, fieldMember->identifierToken - }); - m_fieldMemberBase = fieldMember; - } else { - m_fieldMemberBase = nullptr; - } -} - -bool FindWarningVisitor::visit(QQmlJS::AST::BinaryExpression *) -{ - return true; -} - -void FindWarningVisitor::endVisit(QQmlJS::AST::BinaryExpression *binExp) -{ - if (binExp->op == QSOperator::As && m_fieldMemberBase == binExp->left) - m_fieldMemberBase = binExp; - else - m_fieldMemberBase = nullptr; -} diff --git a/tools/qmllint/findwarnings.h b/tools/qmllint/findwarnings.h deleted file mode 100644 index 30ee47705e..0000000000 --- a/tools/qmllint/findwarnings.h +++ /dev/null @@ -1,127 +0,0 @@ -/**************************************************************************** -** -** 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 - -// -// 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. - -#include "qcoloroutput.h" -#include "checkidentifiers.h" - -#include <QtQmlCompiler/private/qqmljstypedescriptionreader_p.h> -#include <QtQmlCompiler/private/qqmljsscope_p.h> -#include <QtQmlCompiler/private/qqmljsimporter_p.h> -#include <QtQmlCompiler/private/qqmljsimportvisitor_p.h> - -#include <QtQml/private/qqmldirparser_p.h> -#include <QtQml/private/qqmljsastvisitor_p.h> -#include <QtQml/private/qqmljsast_p.h> - -#include <QtCore/qscopedpointer.h> - -class FindWarningVisitor : public QQmlJSImportVisitor -{ - Q_DISABLE_COPY_MOVE(FindWarningVisitor) -public: - explicit FindWarningVisitor( - QQmlJSImporter *importer, QStringList qmltypesFiles, QString code, QString fileName, - bool silent, bool warnUnqualified, bool warnWithStatement, bool warnInheritanceCycle); - ~FindWarningVisitor() override = default; - bool check(); - -private: - QHash<QQmlJS::SourceLocation, SignalHandler> m_signalHandlers; - QQmlJS::SourceLocation m_pendingSingalHandler; - - MemberAccessChains m_memberAccessChains; - - QQmlJS::AST::ExpressionNode *m_fieldMemberBase = nullptr; - QString m_code; - QString m_rootId; - QString m_filePath; - QSet<QString> m_unknownImports; - ColorOutput m_colorOut; - bool m_visitFailed = false; - - bool m_warnUnqualified; - bool m_warnWithStatement; - bool m_warnInheritanceCycle; - - struct OutstandingConnection - { - QString targetName; - QQmlJSScope::Ptr scope; - QQmlJS::AST::UiObjectDefinition *uiod; - }; - - QVarLengthArray<OutstandingConnection, 3> m_outstandingConnections; // Connections whose target we have not encountered - - void checkInheritanceCycle(QQmlJSScope::ConstPtr scope); - void checkGroupedAndAttachedScopes(QQmlJSScope::ConstPtr scope); - void flushPendingSignalParameters(); - - void throwRecursionDepthError() override; - - // work around compiler error in clang11 - using QQmlJSImportVisitor::visit; - using QQmlJSImportVisitor::endVisit; - - // start block/scope handling - bool visit(QQmlJS::AST::ExpressionStatement *ast) override; - void endVisit(QQmlJS::AST::ExpressionStatement *ast) override; - bool visit(QQmlJS::AST::Block *ast) override; - bool visit(QQmlJS::AST::WithStatement *withStatement) override; - - /* --- end block handling --- */ - - bool visit(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; - - // expression handling - bool visit(QQmlJS::AST::IdentifierExpression *idexp) override; - - bool visit(QQmlJS::AST::PatternElement *) override; - bool visit(QQmlJS::AST::FieldMemberExpression *idprop) override; - void endVisit(QQmlJS::AST::FieldMemberExpression *) override; - - bool visit(QQmlJS::AST::BinaryExpression *) override; - void endVisit(QQmlJS::AST::BinaryExpression *) override; -}; - -#endif // FINDUNQUALIFIED_H diff --git a/tools/qmllint/main.cpp b/tools/qmllint/main.cpp index 3fb9e9c324..05dc667232 100644 --- a/tools/qmllint/main.cpp +++ b/tools/qmllint/main.cpp @@ -1,120 +1,83 @@ -/**************************************************************************** -** -** Copyright (C) 2016 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Sergio Martins <sergio.martins@kdab.com> -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the plugins 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 "findwarnings.h" +// Copyright (C) 2016 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Sergio Martins <sergio.martins@kdab.com> +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 -#include <QtQmlCompiler/private/qqmljsresourcefilemapper_p.h> +#include <QtQmlToolingSettings/private/qqmltoolingsettings_p.h> +#include <QtQmlToolingSettings/private/qqmltoolingutils_p.h> -#include <QtQml/private/qqmljslexer_p.h> -#include <QtQml/private/qqmljsparser_p.h> -#include <QtQml/private/qqmljsengine_p.h> -#include <QtQml/private/qqmljsastvisitor_p.h> -#include <QtQml/private/qqmljsast_p.h> +#include <QtQmlCompiler/private/qqmljsresourcefilemapper_p.h> +#include <QtQmlCompiler/private/qqmljscompiler_p.h> +#include <QtQmlCompiler/private/qqmljslinter_p.h> +#include <QtQmlCompiler/private/qqmljsloggingutils_p.h> #include <QtCore/qdebug.h> #include <QtCore/qfile.h> #include <QtCore/qfileinfo.h> #include <QtCore/qcoreapplication.h> #include <QtCore/qdiriterator.h> +#include <QtCore/qjsonobject.h> +#include <QtCore/qjsonarray.h> +#include <QtCore/qjsondocument.h> +#include <QtCore/qscopeguard.h> #if QT_CONFIG(commandlineparser) #include <QtCore/qcommandlineparser.h> #endif -#ifndef QT_BOOTSTRAPPED #include <QtCore/qlibraryinfo.h> -#endif - -static bool lint_file(const QString &filename, const bool silent, const bool warnUnqualified, - const bool warnWithStatement, const bool warnInheritanceCycle, - const QStringList &qmlImportPaths, const QStringList &qmltypesFiles, - const QString &resourceFile) -{ - QFile file(filename); - if (!file.open(QFile::ReadOnly)) { - if (!silent) - qWarning() << "Failed to open file" << filename << file.error(); - return false; - } - QString code = QString::fromUtf8(file.readAll()); - file.close(); +#include <cstdio> - QQmlJS::Engine engine; - QQmlJS::Lexer lexer(&engine); - - QFileInfo info(filename); - const QString lowerSuffix = info.suffix().toLower(); - const bool isESModule = lowerSuffix == QLatin1String("mjs"); - const bool isJavaScript = isESModule || lowerSuffix == QLatin1String("js"); - - lexer.setCode(code, /*lineno = */ 1, /*qmlMode=*/ !isJavaScript); - QQmlJS::Parser parser(&engine); - - bool success = isJavaScript ? (isESModule ? parser.parseModule() : parser.parseProgram()) - : parser.parse(); - - if (!success && !silent) { - const auto diagnosticMessages = parser.diagnosticMessages(); - for (const QQmlJS::DiagnosticMessage &m : diagnosticMessages) { - qWarning().noquote() << QString::fromLatin1("%1:%2 : %3") - .arg(filename).arg(m.loc.startLine).arg(m.message); - } - } +using namespace Qt::StringLiterals; - if (success && !isJavaScript) { - const auto check = [&](QQmlJSResourceFileMapper *mapper) { - QQmlJSImporter importer(qmlImportPaths, mapper); - FindWarningVisitor v { &importer, qmltypesFiles, code, filename, silent, - warnUnqualified, warnWithStatement, warnInheritanceCycle }; - parser.rootNode()->accept(&v); - success = v.check(); - }; +constexpr int JSON_LOGGING_FORMAT_REVISION = 3; - if (resourceFile.isEmpty()) { - check(nullptr); +bool argumentsFromCommandLineAndFile(QStringList& allArguments, const QStringList &arguments) +{ + allArguments.reserve(arguments.size()); + for (const QString &argument : arguments) { + // "@file" doesn't start with a '-' so we can't use QCommandLineParser for it + if (argument.startsWith(u'@')) { + QString optionsFile = argument; + optionsFile.remove(0, 1); + if (optionsFile.isEmpty()) { + qWarning().nospace() << "The @ option requires an input file"; + return false; + } + QFile f(optionsFile); + if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) { + qWarning().nospace() << "Cannot open options file specified with @"; + return false; + } + while (!f.atEnd()) { + QString line = QString::fromLocal8Bit(f.readLine().trimmed()); + if (!line.isEmpty()) + allArguments << line; + } } else { - QQmlJSResourceFileMapper mapper({ resourceFile }); - check(&mapper); + allArguments << argument; } } - - return success; + return true; } int main(int argv, char *argc[]) { + QHashSeed::setDeterministicGlobalSeed(); + QList<QQmlJS::LoggerCategory> categories; + QCoreApplication app(argv, argc); QCoreApplication::setApplicationName("qmllint"); - QCoreApplication::setApplicationVersion("1.0"); -#if QT_CONFIG(commandlineparser) + QCoreApplication::setApplicationVersion(QT_VERSION_STR); QCommandLineParser parser; - parser.setApplicationDescription(QLatin1String("QML syntax verifier")); + QQmlToolingSettings settings(QLatin1String("qmllint")); + parser.setApplicationDescription(QLatin1String(R"(QML syntax verifier and analyzer + +All warnings can be set to three levels: + disable - Fully disables the warning. + info - Displays the warning but does not influence the return code. + warning - Displays the warning and leads to a non-zero exit code if encountered. +)")); + parser.addHelpOption(); parser.addVersionOption(); @@ -122,24 +85,34 @@ int main(int argv, char *argc[]) QLatin1String("Don't output syntax errors")); parser.addOption(silentOption); - QCommandLineOption disableCheckUnqualified(QStringList() << "no-unqualified-id", - QLatin1String("Don't warn about unqualified identifiers")); - parser.addOption(disableCheckUnqualified); + QCommandLineOption jsonOption(QStringList() << "json", + QLatin1String("Write output as JSON to file (or use the special " + "filename '-' to write to stdout)"), + QLatin1String("file"), QString()); + parser.addOption(jsonOption); - QCommandLineOption disableCheckWithStatement(QStringList() << "no-with-statement", - QLatin1String("Don't warn about with statements")); - parser.addOption(disableCheckWithStatement); + QCommandLineOption writeDefaultsOption( + QStringList() << "write-defaults", + QLatin1String("Writes defaults settings to .qmllint.ini and exits (Warning: This " + "will overwrite any existing settings and comments!)")); + parser.addOption(writeDefaultsOption); - QCommandLineOption disableCheckInheritanceCycle(QStringList() << "no-inheritance-cycle", - QLatin1String("Don't warn about inheritance cycles")); + QCommandLineOption ignoreSettings(QStringList() << "ignore-settings", + QLatin1String("Ignores all settings files and only takes " + "command line options into consideration")); + parser.addOption(ignoreSettings); - parser.addOption(disableCheckInheritanceCycle); + QCommandLineOption moduleOption({ QStringLiteral("M"), QStringLiteral("module") }, + QStringLiteral("Lint modules instead of files")); + parser.addOption(moduleOption); QCommandLineOption resourceOption( { QStringLiteral("resource") }, QStringLiteral("Look for related files in the given resource file"), QStringLiteral("resource")); parser.addOption(resourceOption); + const QString &resourceSetting = QLatin1String("ResourcePath"); + settings.addOption(resourceSetting); QCommandLineOption qmlImportPathsOption( QStringList() << "I" @@ -147,72 +120,436 @@ int main(int argv, char *argc[]) QLatin1String("Look for QML modules in specified directory"), QLatin1String("directory")); parser.addOption(qmlImportPathsOption); - - QCommandLineOption qmltypesFilesOption( + const QString qmlImportPathsSetting = QLatin1String("AdditionalQmlImportPaths"); + settings.addOption(qmlImportPathsSetting); + + QCommandLineOption environmentOption( + QStringList() << "E", + QLatin1String("Use the QML_IMPORT_PATH environment variable to look for QML Modules")); + parser.addOption(environmentOption); + + QCommandLineOption qmlImportNoDefault( + QStringList() << "bare", + QLatin1String("Do not include default import directories or the current directory. " + "This may be used to run qmllint on a project using a different Qt version.")); + parser.addOption(qmlImportNoDefault); + const QString qmlImportNoDefaultSetting = QLatin1String("DisableDefaultImports"); + settings.addOption(qmlImportNoDefaultSetting, false); + + QCommandLineOption qmldirFilesOption( QStringList() << "i" << "qmltypes", - QLatin1String("Include the specified qmltypes files. By default, all qmltypes files " - "found in the current directory are used. When this option is set, you " - "have to explicitly add files from the current directory if you want " - "them to be used."), - QLatin1String("qmltypes")); - parser.addOption(qmltypesFilesOption); + QLatin1String("Import the specified qmldir files. By default, the qmldir file found " + "in the current directory is used if present. If no qmldir file is found," + "but qmltypes files are, those are imported instead. When this option is " + "set, you have to explicitly add the qmldir or any qmltypes files in the " + "current directory if you want it to be used. Importing qmltypes files " + "without their corresponding qmldir file is inadvisable."), + QLatin1String("qmldirs")); + parser.addOption(qmldirFilesOption); + const QString qmldirFilesSetting = QLatin1String("OverwriteImportTypes"); + settings.addOption(qmldirFilesSetting); + + QCommandLineOption absolutePath( + QStringList() << "absolute-path", + QLatin1String("Use absolute paths for logging instead of relative ones.")); + absolutePath.setFlags(QCommandLineOption::HiddenFromHelp); + parser.addOption(absolutePath); + + QCommandLineOption fixFile(QStringList() << "f" + << "fix", + QLatin1String("Automatically apply fix suggestions")); + parser.addOption(fixFile); + + QCommandLineOption dryRun(QStringList() << "dry-run", + QLatin1String("Only print out the contents of the file after fix " + "suggestions without applying them")); + parser.addOption(dryRun); + + QCommandLineOption listPluginsOption(QStringList() << "list-plugins", + QLatin1String("List all available plugins")); + parser.addOption(listPluginsOption); + + QCommandLineOption pluginsDisable( + QStringList() << "D" + << "disable-plugins", + QLatin1String("List of qmllint plugins to disable (all to disable all plugins)"), + QLatin1String("plugins")); + parser.addOption(pluginsDisable); + const QString pluginsDisableSetting = QLatin1String("DisablePlugins"); + settings.addOption(pluginsDisableSetting); + + QCommandLineOption pluginPathsOption( + QStringList() << "P" + << "plugin-paths", + QLatin1String("Look for qmllint plugins in specified directory"), + QLatin1String("directory")); + parser.addOption(pluginPathsOption); + + auto levelToString = [](const QQmlJS::LoggerCategory &category) -> QString { + Q_ASSERT(category.isIgnored() || category.level() != QtCriticalMsg); + if (category.isIgnored()) + return QStringLiteral("disable"); + + switch (category.level()) { + case QtInfoMsg: + return QStringLiteral("info"); + case QtWarningMsg: + return QStringLiteral("warning"); + default: + Q_UNREACHABLE(); + break; + } + }; + + auto addCategory = [&](const QQmlJS::LoggerCategory &category) { + categories.push_back(category); + if (category.isDefault()) + return; + QCommandLineOption option( + category.id().name().toString(), + category.description() + + QStringLiteral(" (default: %1)").arg(levelToString(category)), + QStringLiteral("level"), levelToString(category)); + if (category.isIgnored()) + option.setFlags(QCommandLineOption::HiddenFromHelp); + parser.addOption(option); + settings.addOption(QStringLiteral("Warnings/") + category.settingsName(), + levelToString(category)); + }; + + for (const auto &category : QQmlJSLogger::defaultCategories()) { + addCategory(category); + } parser.addPositionalArgument(QLatin1String("files"), QLatin1String("list of qml or js files to verify")); - parser.process(app); + QStringList arguments; + if (!argumentsFromCommandLineAndFile(arguments, app.arguments())) { + // argumentsFromCommandLine already printed any necessary warnings. + return 1; + } + + if (!parser.parse(arguments)) { + qWarning().noquote() << parser.errorText(); + return 1; + } + + // Since we can't use QCommandLineParser::process(), we need to handle version and help manually + if (parser.isSet("version")) + parser.showVersion(); + + if (parser.isSet("help") || parser.isSet("help-all")) + parser.showHelp(0); + + if (parser.isSet(writeDefaultsOption)) { + return settings.writeDefaults() ? 0 : 1; + } + + auto updateLogLevels = [&]() { + for (auto &category : categories) { + if (category.isDefault()) + continue; + + const QString &key = category.id().name().toString(); + const QString &settingsName = QStringLiteral("Warnings/") + category.settingsName(); + if (parser.isSet(key) || settings.isSet(settingsName)) { + const QString value = parser.isSet(key) ? parser.value(key) + : settings.value(settingsName).toString(); + + // Do not try to set the levels if it's due to a default config option. + // This way we can tell which options have actually been overwritten by the user. + if (levelToString(category) == value && !parser.isSet(key)) + continue; + + if (value == "disable"_L1) { + category.setLevel(QtCriticalMsg); + category.setIgnored(true); + } else if (value == "info"_L1) { + category.setLevel(QtInfoMsg); + category.setIgnored(false); + } else if (value == "warning"_L1) { + category.setLevel(QtWarningMsg); + category.setIgnored(false); + } else { + qWarning() << "Invalid logging level" << value << "provided for" + << category.id().name().toString() + << "(allowed are: disable, info, warning)"; + parser.showHelp(-1); + } + } + } + }; + + bool silent = parser.isSet(silentOption); + bool useAbsolutePath = parser.isSet(absolutePath); + bool useJson = parser.isSet(jsonOption); + + // use host qml import path as a sane default if not explicitly disabled + QStringList defaultImportPaths = { QDir::currentPath() }; + + if (parser.isSet(resourceOption)) { + defaultImportPaths.append(QLatin1String(":/qt-project.org/imports")); + defaultImportPaths.append(QLatin1String(":/qt/qml")); + }; + + defaultImportPaths.append(QLibraryInfo::path(QLibraryInfo::QmlImportsPath)); + + QStringList qmlImportPaths = + parser.isSet(qmlImportNoDefault) ? QStringList {} : defaultImportPaths; + + QStringList defaultQmldirFiles; + if (parser.isSet(qmldirFilesOption)) { + defaultQmldirFiles = parser.values(qmldirFilesOption); + } else if (!parser.isSet(qmlImportNoDefault)){ + // If nothing given explicitly, use the qmldir file from the current directory. + QFileInfo qmldirFile(QStringLiteral("qmldir")); + if (qmldirFile.isFile()) { + defaultQmldirFiles.append(qmldirFile.absoluteFilePath()); + } else { + // If no qmldir file is found, use the qmltypes files + // from the current directory for backwards compatibility. + QDirIterator it(".", {"*.qmltypes"}, QDir::Files); + while (it.hasNext()) { + it.next(); + defaultQmldirFiles.append(it.fileInfo().absoluteFilePath()); + } + } + } + QStringList qmldirFiles = defaultQmldirFiles; + + const QStringList defaultResourceFiles = + parser.isSet(resourceOption) ? parser.values(resourceOption) : QStringList {}; + QStringList resourceFiles = defaultResourceFiles; + + bool success = true; + + QStringList pluginPaths = { QQmlJSLinter::defaultPluginPath() }; + + if (parser.isSet(pluginPathsOption)) + pluginPaths << parser.values(pluginPathsOption); + + QQmlJSLinter linter(qmlImportPaths, pluginPaths, useAbsolutePath); + + for (const QQmlJSLinter::Plugin &plugin : linter.plugins()) { + for (const QQmlJS::LoggerCategory &category : plugin.categories()) + addCategory(category); + } + + if (!parser.unknownOptionNames().isEmpty()) + parser.process(app); + + updateLogLevels(); + + if (parser.isSet(listPluginsOption)) { + const std::vector<QQmlJSLinter::Plugin> &plugins = linter.plugins(); + if (!plugins.empty()) { + qInfo().nospace().noquote() << "Plugin\t\t\tBuilt-in?\tVersion\tAuthor\t\tDescription"; + for (const QQmlJSLinter::Plugin &plugin : plugins) { + qInfo().nospace().noquote() + << plugin.name() << "\t\t\t" << (plugin.isBuiltin() ? "Yes" : "No") + << "\t\t" << plugin.version() << "\t" << plugin.author() << "\t\t" + << plugin.description(); + } + } else { + qWarning() << "No plugins installed."; + } + return 0; + } const auto positionalArguments = parser.positionalArguments(); if (positionalArguments.isEmpty()) { parser.showHelp(-1); } - bool silent = parser.isSet(silentOption); - bool warnUnqualified = !parser.isSet(disableCheckUnqualified); - bool warnWithStatement = !parser.isSet(disableCheckWithStatement); - bool warnInheritanceCycle = !parser.isSet(disableCheckInheritanceCycle); - - // use host qml import path as a sane default if nothing else has been provided - QStringList qmlImportPaths = parser.isSet(qmlImportPathsOption) - ? parser.values(qmlImportPathsOption) -# ifndef QT_BOOTSTRAPPED - : QStringList { QLibraryInfo::path(QLibraryInfo::QmlImportsPath), QDir::currentPath() }; -# else - : QStringList { QDir::currentPath() }; -# endif - - QStringList qmltypesFiles; - if (parser.isSet(qmltypesFilesOption)) { - qmltypesFiles = parser.values(qmltypesFilesOption); - } else { - // If none are given explicitly, use the qmltypes files from the current directory. - QDirIterator it(".", {"*.qmltypes"}, QDir::Files); - while (it.hasNext()) { - it.next(); - qmltypesFiles.append(it.fileInfo().absoluteFilePath()); + QJsonArray jsonFiles; + + for (const QString &filename : positionalArguments) { + if (!parser.isSet(ignoreSettings)) + settings.search(filename); + updateLogLevels(); + + const QDir fileDir = QFileInfo(filename).absoluteDir(); + auto addAbsolutePaths = [&](QStringList &list, const QStringList &entries) { + for (const QString &file : entries) + list << (QFileInfo(file).isAbsolute() ? file : fileDir.filePath(file)); + }; + + resourceFiles = defaultResourceFiles; + + addAbsolutePaths(resourceFiles, settings.value(resourceSetting).toStringList()); + + qmldirFiles = defaultQmldirFiles; + if (settings.isSet(qmldirFilesSetting) + && !settings.value(qmldirFilesSetting).toStringList().isEmpty()) { + qmldirFiles = {}; + addAbsolutePaths(qmldirFiles, settings.value(qmldirFilesSetting).toStringList()); + } + + if (parser.isSet(qmlImportNoDefault) + || (settings.isSet(qmlImportNoDefaultSetting) + && settings.value(qmlImportNoDefaultSetting).toBool())) { + qmlImportPaths = {}; + } else { + qmlImportPaths = defaultImportPaths; + } + + if (parser.isSet(qmlImportPathsOption)) + qmlImportPaths << parser.values(qmlImportPathsOption); + if (parser.isSet(environmentOption)) { + if (silent) { + qmlImportPaths << qEnvironmentVariable("QML_IMPORT_PATH") + .split(QDir::separator(), Qt::SkipEmptyParts) + << qEnvironmentVariable("QML2_IMPORT_PATH") + .split(QDir::separator(), Qt::SkipEmptyParts); + } else { + if (const QStringList dirsFromEnv = + QQmlToolingUtils::getAndWarnForInvalidDirsFromEnv(u"QML_IMPORT_PATH"_s); + !dirsFromEnv.isEmpty()) { + qInfo().nospace().noquote() + << "Using import directories passed from environment variable " + "\"QML_IMPORT_PATH\": \"" + << dirsFromEnv.join(u"\", \""_s) << "\"."; + qmlImportPaths << dirsFromEnv; + } + if (const QStringList dirsFromEnv = + QQmlToolingUtils::getAndWarnForInvalidDirsFromEnv( + u"QML2_IMPORT_PATH"_s); + !dirsFromEnv.isEmpty()) { + qInfo().nospace().noquote() << "Using import directories passed from the " + "deprecated environment variable " + "\"QML2_IMPORT_PATH\": \"" + << dirsFromEnv.join(u"\", \""_s) << "\"."; + qmlImportPaths << dirsFromEnv; + } + } + } + + addAbsolutePaths(qmlImportPaths, settings.value(qmlImportPathsSetting).toStringList()); + + QSet<QString> disabledPlugins; + + if (parser.isSet(pluginsDisable)) { + for (const QString &plugin : parser.values(pluginsDisable)) + disabledPlugins << plugin.toLower(); + } + + if (settings.isSet(pluginsDisableSetting)) { + for (const QString &plugin : settings.value(pluginsDisableSetting).toStringList()) + disabledPlugins << plugin.toLower(); + } + + linter.setPluginsEnabled(!disabledPlugins.contains("all")); + + if (!linter.pluginsEnabled()) + continue; + + auto &plugins = linter.plugins(); + + for (auto &plugin : plugins) + plugin.setEnabled(!disabledPlugins.contains(plugin.name().toLower())); + + const bool isFixing = parser.isSet(fixFile); + + QQmlJSLinter::LintResult lintResult; + + if (parser.isSet(moduleOption)) { + lintResult = linter.lintModule(filename, silent, useJson ? &jsonFiles : nullptr, + qmlImportPaths, resourceFiles); + } else { + lintResult = linter.lintFile(filename, nullptr, silent || isFixing, + useJson ? &jsonFiles : nullptr, qmlImportPaths, + qmldirFiles, resourceFiles, categories); + } + success &= (lintResult == QQmlJSLinter::LintSuccess); + + if (isFixing) { + if (lintResult != QQmlJSLinter::LintSuccess && lintResult != QQmlJSLinter::HasWarnings) + continue; + + QString fixedCode; + const QQmlJSLinter::FixResult result = linter.applyFixes(&fixedCode, silent); + + if (result != QQmlJSLinter::NothingToFix && result != QQmlJSLinter::FixSuccess) { + success = false; + continue; + } + + if (parser.isSet(dryRun)) { + QTextStream(stdout) << fixedCode; + } else { + if (result == QQmlJSLinter::NothingToFix) { + if (!silent) + qWarning().nospace() << "Nothing to fix in " << filename; + continue; + } + + const QString backupFile = filename + u".bak"_s; + if (QFile::exists(backupFile) && !QFile::remove(backupFile)) { + if (!silent) { + qWarning().nospace() << "Failed to remove old backup file " << backupFile + << ", aborting"; + } + success = false; + continue; + } + if (!QFile::copy(filename, backupFile)) { + if (!silent) { + qWarning().nospace() + << "Failed to create backup file " << backupFile << ", aborting"; + } + success = false; + continue; + } + + QFile file(filename); + if (!file.open(QIODevice::WriteOnly)) { + if (!silent) { + qWarning().nospace() << "Failed to open " << filename + << " for writing:" << file.errorString(); + } + success = false; + continue; + } + + const QByteArray data = fixedCode.toUtf8(); + if (file.write(data) != data.size()) { + if (!silent) { + qWarning().nospace() << "Failed to write new contents to " << filename + << ": " << file.errorString(); + } + success = false; + continue; + } + if (!silent) { + qDebug().nospace() << "Applied fixes to " << filename << ". Backup created at " + << backupFile; + } + } } } - const QString resourceFile = parser.value(resourceOption); + if (useJson) { + QJsonObject result; -#else - bool silent = false; - bool warnUnqualified = true; - bool warnWithStatement = true; - bool warnInheritanceCycle = true; - QStringList qmlImportPahs {}; - QStringList qmltypesFiles {}; -#endif - bool success = true; -#if QT_CONFIG(commandlineparser) - for (const QString &filename : positionalArguments) -#else - const auto arguments = app.arguments(); - for (const QString &filename : arguments) -#endif - success &= lint_file(filename, silent, warnUnqualified, warnWithStatement, - warnInheritanceCycle, qmlImportPaths, qmltypesFiles, resourceFile); + result[u"revision"_s] = JSON_LOGGING_FORMAT_REVISION; + result[u"files"_s] = jsonFiles; + + QString fileName = parser.value(jsonOption); + + const QByteArray json = QJsonDocument(result).toJson(QJsonDocument::Compact); + + if (fileName == u"-") { + QTextStream(stdout) << QString::fromUtf8(json); + } else { + QFile file(fileName); + if (file.open(QFile::WriteOnly)) + file.write(json); + else + success = false; + } + } return success ? 0 : -1; } diff --git a/tools/qmllint/qcoloroutput.cpp b/tools/qmllint/qcoloroutput.cpp deleted file mode 100644 index 22783ef1b9..0000000000 --- a/tools/qmllint/qcoloroutput.cpp +++ /dev/null @@ -1,336 +0,0 @@ -/**************************************************************************** -** -** 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 "qcoloroutput.h" - -#include <QtCore/qfile.h> -#include <QtCore/qhash.h> - -#ifndef Q_OS_WIN -#include <unistd.h> -#endif - -class ColorOutputPrivate -{ -public: - ColorOutputPrivate(bool silent) : m_currentColorID(-1), m_silent(silent) - { - /* - 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); - m_coloringEnabled = isColoringPossible(); - } - - 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) - { - const ushort escapeChar = 0x1B; - QString result; - result.append(QChar(escapeChar)); - result.append(QLatin1Char('[')); - result.append(in); - result.append(QLatin1Char('m')); - return result; - } - - void insertColor(int id, ColorOutput::ColorCode code) { m_colorMapping.insert(id, code); } - ColorOutput::ColorCode color(int id) const { return m_colorMapping.value(id); } - bool containsColor(int id) const { return m_colorMapping.contains(id); } - - bool isSilent() const { return m_silent; } - void setCurrentColorID(int colorId) { m_currentColorID = colorId; } - - bool coloringEnabled() const { return m_coloringEnabled; } - -private: - QFile m_out; - ColorOutput::ColorMapping m_colorMapping; - int m_currentColorID; - bool m_coloringEnabled; - bool m_silent; - - /*! - 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 - \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. - */ - -/*! - Constructs a ColorOutput instance, ready for use. - */ -ColorOutput::ColorOutput(bool silent) : d(new ColorOutputPrivate(silent)) {} - -// must be here so that QScopedPointer has access to the complete type -ColorOutput::~ColorOutput() = default; - -/*! - Sends \a message to \c stderr, using the color looked up in the color mapping using \a colorID. - - If \a color isn't available in the color mapping, 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) -{ - if (!d->isSilent()) - d->write(colorify(message, colorID)); -} - -void ColorOutput::writePrefixedMessage(const QString &message, MessageColors type, - const QString &prefix) -{ - static const QStringList prefixes = { - QStringLiteral("Error"), - QStringLiteral("Warning"), - QStringLiteral("Info"), - QStringLiteral("Normal"), - QStringLiteral("Hint"), - }; - - Q_ASSERT(prefixes.length() > qsizetype(type)); - Q_ASSERT(prefix.isEmpty() || prefix.front().isUpper()); - write((prefix.isEmpty() ? prefixes[type] : prefix) + QStringLiteral(": "), type); - writeUncolored(message); -} - -/*! - 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) -{ - if (!d->isSilent()) - 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->containsColor(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->setCurrentColorID(colorID); - - if (d->coloringEnabled() && colorID != -1) { - const int color = d->color(colorID); - - /* If DefaultColor is set, we don't want to color it. */ - if (color & DefaultColor) - return message; - - const int foregroundCode = (color & ForegroundMask) >> ForegroundShift; - const int backgroundCode = (color & BackgroundMask) >> BackgroundShift; - QString finalMessage; - bool closureNeeded = false; - - if (foregroundCode > 0) { - finalMessage.append( - ColorOutputPrivate::escapeCode( - QLatin1String(ColorOutputPrivate::foregrounds[foregroundCode - 1]))); - closureNeeded = true; - } - - if (backgroundCode > 0) { - finalMessage.append( - ColorOutputPrivate::escapeCode( - QLatin1String(ColorOutputPrivate::backgrounds[backgroundCode - 1]))); - closureNeeded = true; - } - - finalMessage.append(message); - - if (closureNeeded) - finalMessage.append(ColorOutputPrivate::escapeCode(QLatin1String("0"))); - - return finalMessage; - } - - return message; -} - -/*! - Adds a color mapping from \a colorID to \a colorCode, for this ColorOutput instance. - */ -void ColorOutput::insertMapping(int colorID, const ColorCode colorCode) -{ - d->insertColor(colorID, colorCode); -} diff --git a/tools/qmllint/qcoloroutput.h b/tools/qmllint/qcoloroutput.h deleted file mode 100644 index 41b8751432..0000000000 --- a/tools/qmllint/qcoloroutput.h +++ /dev/null @@ -1,119 +0,0 @@ -/**************************************************************************** -** -** 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 QCOLOROUTPUT_H -#define QCOLOROUTPUT_H - -// -// 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. - -#include <QtCore/qglobal.h> -#include <QtCore/qscopedpointer.h> -#include <QtCore/qstring.h> - -class ColorOutputPrivate; - -enum MessageColors -{ - Error, - Warning, - Info, - Normal, - Hint -}; - -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 - }; - - using ColorCode = QFlags<ColorCodeComponent>; - using ColorMapping = QHash<int, ColorCode>; - - ColorOutput(bool silent); - ~ColorOutput(); - - void insertMapping(int colorID, ColorCode colorCode); - - void writeUncolored(const QString &message); - void write(const QString &message, int color = -1); - void writePrefixedMessage(const QString &message, MessageColors type, - const QString &prefix = QString()); - QString colorify(const QString &message, int color = -1) const; - -private: - QScopedPointer<ColorOutputPrivate> d; - Q_DISABLE_COPY_MOVE(ColorOutput) -}; - -Q_DECLARE_OPERATORS_FOR_FLAGS(ColorOutput::ColorCode) - -#endif // QCOLOROUTPUT_H diff --git a/tools/qmlls/CMakeLists.txt b/tools/qmlls/CMakeLists.txt new file mode 100644 index 0000000000..d61a18582e --- /dev/null +++ b/tools/qmlls/CMakeLists.txt @@ -0,0 +1,23 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +##################################################################### +## qmlls Tool: +##################################################################### + +qt_internal_add_app(qmlls + TARGET_DESCRIPTION "QML Language Server" + SOURCES + qmllanguageservertool.cpp + DEFINES + QT_USE_QSTRINGBUILDER + LIBRARIES + Qt::QmlLSPrivate + Qt::QmlToolingSettingsPrivate +) +set_target_properties(qmlls PROPERTIES WIN32_EXECUTABLE FALSE) + +if(NOT QT6_IS_SHARED_LIBS_BUILD) + qt_import_plugins(qmlls INCLUDE Qt::QmlLSQuickPlugin) + target_link_libraries(qmlls PRIVATE Qt::QmlLSQuickPlugin) +endif() diff --git a/tools/qmlls/qmllanguageservertool.cpp b/tools/qmlls/qmllanguageservertool.cpp new file mode 100644 index 0000000000..34138638b7 --- /dev/null +++ b/tools/qmlls/qmllanguageservertool.cpp @@ -0,0 +1,315 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include <QtQmlLS/private/qqmllanguageserver_p.h> +#include <QtCore/qdebug.h> +#include <QtCore/qfile.h> +#include <QtCore/qdir.h> +#include <QtCore/qfileinfo.h> +#include <QtCore/qcoreapplication.h> +#include <QtQmlToolingSettings/private/qqmltoolingsettings_p.h> +#include <QtQmlToolingSettings/private/qqmltoolingutils_p.h> +#include <QtCore/qdiriterator.h> +#include <QtCore/qjsonobject.h> +#include <QtCore/qjsonarray.h> +#include <QtCore/qjsondocument.h> +#include <QtCore/qmutex.h> +#include <QtCore/QMutexLocker> +#include <QtCore/qscopedpointer.h> +#include <QtCore/qrunnable.h> +#include <QtCore/qthreadpool.h> +#include <QtCore/qtimer.h> + +#include <QtJsonRpc/private/qhttpmessagestreamparser_p.h> + +#include <QtQmlCompiler/private/qqmljsresourcefilemapper_p.h> +#include <QtQmlCompiler/private/qqmljscompiler_p.h> +#include <QtQmlCompiler/private/qqmljslogger_p.h> +#include <QtQmlCompiler/private/qqmljsscope_p.h> +#include <QtQmlCompiler/private/qqmljsimporter_p.h> +#if QT_CONFIG(commandlineparser) +# include <QtCore/qcommandlineparser.h> +#endif + +#ifndef QT_BOOTSTRAPPED +# include <QtCore/qlibraryinfo.h> +#endif + +#include <iostream> +#ifdef Q_OS_WIN32 +# include <fcntl.h> +# include <io.h> +#endif + +using namespace QmlLsp; + +QFile *logFile = nullptr; +QBasicMutex *logFileLock = nullptr; + +class StdinReader : public QObject +{ + Q_OBJECT +public: + void run() + { + auto guard = qScopeGuard([this]() { emit eof(); }); + const constexpr qsizetype bufSize = 1024; + qsizetype bytesInBuf = 0; + char bufferData[2 * bufSize]; + char *buffer = static_cast<char *>(bufferData); + + auto trySend = [this, &bytesInBuf, buffer]() { + if (bytesInBuf == 0) + return; + qsizetype toSend = bytesInBuf; + bytesInBuf = 0; + QByteArray dataToSend(buffer, toSend); + emit receivedData(dataToSend); + }; + QHttpMessageStreamParser streamParser( + [](const QByteArray &, const QByteArray &) { /* just a header, do nothing */ }, + [&trySend](const QByteArray &) { + // message body + trySend(); + }, + [&trySend](QtMsgType, QString) { + // there was an error + trySend(); + }, + QHttpMessageStreamParser::UNBUFFERED); + + while (std::cin.get(buffer[bytesInBuf])) { // should poll/select and process events + qsizetype readNow = std::cin.readsome(buffer + bytesInBuf + 1, bufSize) + 1; + QByteArray toAdd(buffer + bytesInBuf, readNow); + bytesInBuf += readNow; + if (bytesInBuf >= bufSize) + trySend(); + streamParser.receiveData(toAdd); + } + trySend(); + } +signals: + void receivedData(const QByteArray &data); + void eof(); +}; + +// To debug: +// +// * simple logging can be redirected to a file +// passing -l <file> to the qmlls command +// +// * more complex debugging can use named pipes: +// +// mkfifo qmllsIn +// mkfifo qmllsOut +// +// this together with a qmllsEcho script that can be defined as +// +// #!/bin/sh +// cat -u < ~/qmllsOut & +// cat -u > ~/qmllsIn +// +// allows to use qmllsEcho as lsp server, and still easily start +// it in a terminal +// +// qmlls < ~/qmllsIn > ~/qmllsOut +// +// * statup can be slowed down to have the time to attach via the +// -w <nSeconds> flag. + +int main(int argv, char *argc[]) +{ +#ifdef Q_OS_WIN32 + // windows does not open stdin/stdout in binary mode by default + int err = _setmode(_fileno(stdout), _O_BINARY); + if (err == -1) + perror("Cannot set mode for stdout"); + err = _setmode(_fileno(stdin), _O_BINARY); + if (err == -1) + perror("Cannot set mode for stdin"); +#endif + + QHashSeed::setDeterministicGlobalSeed(); + QCoreApplication app(argv, argc); + QCoreApplication::setApplicationName("qmlls"); + QCoreApplication::setApplicationVersion(QT_VERSION_STR); + + QCommandLineParser parser; + QQmlToolingSettings settings(QLatin1String("qmlls")); + parser.setApplicationDescription(QLatin1String(R"(QML languageserver)")); + + parser.addHelpOption(); + QCommandLineOption waitOption(QStringList() << "w" + << "wait", + QLatin1String("Waits the given number of seconds before startup"), + QLatin1String("waitSeconds")); + parser.addOption(waitOption); + + QCommandLineOption verboseOption( + QStringList() << "v" + << "verbose", + QLatin1String("Outputs extra information on the operations being performed")); + parser.addOption(verboseOption); + + QCommandLineOption logFileOption(QStringList() << "l" + << "log-file", + QLatin1String("Writes logging to the given file"), + QLatin1String("logFile")); + parser.addOption(logFileOption); + + QString buildDir = QStringLiteral(u"buildDir"); + QCommandLineOption buildDirOption( + QStringList() << "b" + << "build-dir", + QLatin1String("Adds a build dir to look up for qml information"), buildDir); + parser.addOption(buildDirOption); + settings.addOption(buildDir); + + QString qmlImportPath = QStringLiteral(u"qml-import-path"); + QCommandLineOption qmlImportPathOption( + QStringList() << "I", QLatin1String("Look for QML modules in the specified directory"), + qmlImportPath); + parser.addOption(qmlImportPathOption); + + QCommandLineOption environmentOption( + QStringList() << "E", + QLatin1String("Use the QML_IMPORT_PATH environment variable to look for QML Modules")); + parser.addOption(environmentOption); + + QCommandLineOption writeDefaultsOption( + QStringList() << "write-defaults", + QLatin1String("Writes defaults settings to .qmlls.ini and exits (Warning: This " + "will overwrite any existing settings and comments!)")); + parser.addOption(writeDefaultsOption); + + QCommandLineOption ignoreSettings(QStringList() << "ignore-settings", + QLatin1String("Ignores all settings files and only takes " + "command line options into consideration")); + parser.addOption(ignoreSettings); + + QCommandLineOption noCMakeCallsOption( + QStringList() << "no-cmake-calls", + QLatin1String("Disables automatic CMake rebuilds and C++ file watching.")); + parser.addOption(noCMakeCallsOption); + settings.addOption("no-cmake-calls", "false"); + + parser.process(app); + + if (parser.isSet(writeDefaultsOption)) { + return settings.writeDefaults() ? 0 : 1; + } + if (parser.isSet(logFileOption)) { + QString fileName = parser.value(logFileOption); + qInfo() << "will log to" << fileName; + logFile = new QFile(fileName); + logFileLock = new QMutex; + logFile->open(QFile::WriteOnly | QFile::Truncate | QFile::Text); + qInstallMessageHandler([](QtMsgType t, const QMessageLogContext &, const QString &msg) { + QMutexLocker l(logFileLock); + logFile->write(QString::number(int(t)).toUtf8()); + logFile->write(" "); + logFile->write(msg.toUtf8()); + logFile->write("\n"); + logFile->flush(); + }); + } + if (parser.isSet(verboseOption)) + QLoggingCategory::setFilterRules("qt.languageserver*.debug=true\n"); + if (parser.isSet(waitOption)) { + int waitSeconds = parser.value(waitOption).toInt(); + if (waitSeconds > 0) + qDebug() << "waiting"; + QThread::sleep(waitSeconds); + qDebug() << "starting"; + } + QMutex writeMutex; + QQmlLanguageServer qmlServer( + [&writeMutex](const QByteArray &data) { + QMutexLocker l(&writeMutex); + std::cout.write(data.constData(), data.size()); + std::cout.flush(); + }, + (parser.isSet(ignoreSettings) ? nullptr : &settings)); + + const bool disableCMakeCallsViaEnvironment = + qmlGetConfigOption<bool, qmlConvertBoolConfigOption>("QMLLS_NO_CMAKE_CALLS"); + + if (disableCMakeCallsViaEnvironment || parser.isSet(noCMakeCallsOption)) { + if (disableCMakeCallsViaEnvironment) { + qWarning() << "Disabling CMake calls via QMLLS_NO_CMAKE_CALLS environment variable."; + } else { + qWarning() << "Disabling CMake calls via command line switch."; + } + + qmlServer.codeModel()->disableCMakeCalls(); + } + + if (parser.isSet(buildDirOption)) { + const QStringList dirs = + QQmlToolingUtils::getAndWarnForInvalidDirsFromOption(parser, buildDirOption); + + qInfo().nospace().noquote() + << "Using build directories passed by -b: \"" << dirs.join(u"\", \""_s) << "\"."; + + qmlServer.codeModel()->setBuildPathsForRootUrl(QByteArray(), dirs); + } else if (QStringList dirsFromEnv = + QQmlToolingUtils::getAndWarnForInvalidDirsFromEnv("QMLLS_BUILD_DIRS"); + !dirsFromEnv.isEmpty()) { + + // warn now at qmlls startup that those directories will be used later in qqmlcodemodel when + // searching for build folders. + qInfo().nospace().noquote() << "Using build directories passed from environment variable " + "\"QMLLS_BUILD_DIRS\": \"" + << dirsFromEnv.join(u"\", \""_s) << "\"."; + + } else { + qInfo() << "Using the build directories found in the .qmlls.ini file. Your build folder " + "might not be found if no .qmlls.ini files are present in the root source " + "folder."; + } + QStringList importPaths{ QLibraryInfo::path(QLibraryInfo::QmlImportsPath) }; + if (parser.isSet(qmlImportPathOption)) { + const QStringList pathsFromOption = + QQmlToolingUtils::getAndWarnForInvalidDirsFromOption(parser, qmlImportPathOption); + qInfo().nospace().noquote() << "Using import directories passed by -I: \"" + << pathsFromOption.join(u"\", \""_s) << "\"."; + importPaths << pathsFromOption; + } + if (parser.isSet(environmentOption)) { + if (const QStringList dirsFromEnv = + QQmlToolingUtils::getAndWarnForInvalidDirsFromEnv(u"QML_IMPORT_PATH"_s); + !dirsFromEnv.isEmpty()) { + qInfo().nospace().noquote() + << "Using import directories passed from environment variable " + "\"QML_IMPORT_PATH\": \"" + << dirsFromEnv.join(u"\", \""_s) << "\"."; + importPaths << dirsFromEnv; + } + + if (const QStringList dirsFromEnv2 = + QQmlToolingUtils::getAndWarnForInvalidDirsFromEnv(u"QML2_IMPORT_PATH"_s); + !dirsFromEnv2.isEmpty()) { + qInfo().nospace().noquote() + << "Using import directories passed from the deprecated environment variable " + "\"QML2_IMPORT_PATH\": \"" + << dirsFromEnv2.join(u"\", \""_s) << "\"."; + importPaths << dirsFromEnv2; + } + } + qmlServer.codeModel()->setImportPaths(importPaths); + + StdinReader r; + QObject::connect(&r, &StdinReader::receivedData, + qmlServer.server(), &QLanguageServer::receiveData); + QObject::connect(&r, &StdinReader::eof, &app, [&app]() { + QTimer::singleShot(100, &app, []() { + QCoreApplication::processEvents(); + QCoreApplication::exit(); + }); + }); + QThreadPool::globalInstance()->start([&r]() { r.run(); }); + app.exec(); + return qmlServer.returnValue(); +} + +#include "qmllanguageservertool.moc" diff --git a/tools/qmlplugindump/.prev_CMakeLists.txt b/tools/qmlplugindump/.prev_CMakeLists.txt deleted file mode 100644 index c4747ea3ab..0000000000 --- a/tools/qmlplugindump/.prev_CMakeLists.txt +++ /dev/null @@ -1,39 +0,0 @@ -# Generated from qmlplugindump.pro. - -##################################################################### -## qmlplugindump Tool: -##################################################################### - -qt_get_tool_target_name(target_name qmlplugindump) -qt_internal_add_tool(${target_name} - TARGET_DESCRIPTION "QML Plugin Metadata Dumper" - SOURCES - ../../src/qmlcompiler/qqmljsstreamwriter.cpp ../../src/qmlcompiler/qqmljsstreamwriter_p.h - main.cpp - qmltypereader.cpp qmltypereader.h - INCLUDE_DIRECTORIES - ../../src/qmlcompiler - PUBLIC_LIBRARIES - Qt::CorePrivate - Qt::Gui - Qt::Qml - Qt::QmlPrivate - Qt::QuickPrivate -) - -#### Keys ignored in scope 1:.:.:qmlplugindump.pro:<TRUE>: -# QMAKE_TARGET_DESCRIPTION = "QML" "Plugin" "Metadata" "Dumper" -# QTPLUGIN.platforms = "qminimal" - -## Scopes: -##################################################################### - -qt_internal_extend_target(${target_name} CONDITION TARGET Qt::Widgets - PUBLIC_LIBRARIES - Qt::Widgets -) - -qt_internal_extend_target(${target_name} CONDITION MACOS - LINK_OPTIONS - "-Wl,-sectcreate,__TEXT,__info_plist,$$shell_quote$$PWD/Info.plist" -) diff --git a/tools/qmlplugindump/CMakeLists.txt b/tools/qmlplugindump/CMakeLists.txt index c36d743611..c2788f03a0 100644 --- a/tools/qmlplugindump/CMakeLists.txt +++ b/tools/qmlplugindump/CMakeLists.txt @@ -1,3 +1,6 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + # Generated from qmlplugindump.pro. ##################################################################### @@ -9,18 +12,17 @@ qt_internal_add_tool(${target_name} TARGET_DESCRIPTION "QML Plugin Metadata Dumper" TOOLS_TARGET Qml # special case SOURCES - ../../src/qmlcompiler/qqmljsstreamwriter.cpp ../../src/qmlcompiler/qqmljsstreamwriter_p.h main.cpp qmltypereader.cpp qmltypereader.h - INCLUDE_DIRECTORIES - ../../src/qmlcompiler - PUBLIC_LIBRARIES + LIBRARIES + Qt::QmlTypeRegistrarPrivate Qt::CorePrivate Qt::Gui Qt::Qml Qt::QmlPrivate Qt::QuickPrivate ) +qt_internal_return_unless_building_tools() #### Keys ignored in scope 1:.:.:qmlplugindump.pro:<TRUE>: # QMAKE_TARGET_DESCRIPTION = "QML" "Plugin" "Metadata" "Dumper" diff --git a/tools/qmlplugindump/main.cpp b/tools/qmlplugindump/main.cpp index 9ee6db39da..ead1e729b9 100644 --- a/tools/qmlplugindump/main.cpp +++ b/tools/qmlplugindump/main.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 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$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include <QtQml/qqmlengine.h> #include <QtQml/private/qqmlengine_p.h> @@ -54,13 +29,14 @@ #include <QtCore/QProcess> #include <QtCore/private/qobject_p.h> #include <QtCore/private/qmetaobject_p.h> +#include <QtQmlTypeRegistrar/private/qqmljsstreamwriter_p.h> +#include <QtQml/private/qqmlsignalnames_p.h> #include <QRegularExpression> #include <iostream> #include <algorithm> #include "qmltypereader.h" -#include "qqmljsstreamwriter_p.h" #ifdef QT_SIMULATOR #include <QtGui/private/qsimulatorconnection_p.h> @@ -93,13 +69,6 @@ QString inObjectInstantiation; } -static QString enquote(const QString &string) -{ - QString s = string; - return QString("\"%1\"").arg(s.replace(QLatin1Char('\\'), QLatin1String("\\\\")) - .replace(QLatin1Char('"'),QLatin1String("\\\""))); -} - struct QmlVersionInfo { QString pluginImportUri; @@ -261,7 +230,8 @@ QSet<const QMetaObject *> collectReachableMetaObjects(QQmlEngine *engine, if (ty.isSingleton()) singletons.insert(ty.baseMetaObject()); if (!ty.isComposite()) { - qmlTypesByCppName[ty.baseMetaObject()->className()].insert(ty); + if (const QMetaObject *mo = ty.baseMetaObject()) + qmlTypesByCppName[mo->className()].insert(ty); collectReachableMetaObjects(QQmlEnginePrivate::get(engine), ty, &metas, info); } else { compositeTypes[ty.elementName()].append(ty); @@ -292,7 +262,7 @@ QSet<const QMetaObject *> collectReachableMetaObjects(QQmlEngine *engine, QObject *object = nullptr; if (ty.isSingleton()) { - QQmlType::SingletonInstanceInfo *siinfo = ty.singletonInstanceInfo(); + QQmlType::SingletonInstanceInfo::ConstPtr siinfo = ty.singletonInstanceInfo(); if (!siinfo) { std::cerr << "Internal error, " << qPrintable(tyName) << "(" << qPrintable( QString::fromUtf8(ty.typeName()) ) << ")" @@ -302,7 +272,8 @@ QSet<const QMetaObject *> collectReachableMetaObjects(QQmlEngine *engine, if (ty.isQObjectSingleton()) { if (verbose) std::cerr << "Trying to get singleton for " << qPrintable(tyName) - << " (" << qPrintable( siinfo->typeName ) << ")" << std::endl; + << " (" << qPrintable( QString::fromUtf8(siinfo->typeName) ) + << ")" << std::endl; collectReachableMetaObjects(object, &metas, info); object = QQmlEnginePrivate::get(engine)->singletonInstance<QObject*>(ty); } else { @@ -372,7 +343,7 @@ public: relocatableModuleUri = uri; } - QString getExportString(const QQmlType &type, const QmlVersionInfo &versionInfo) + QByteArray getExportString(const QQmlType &type, const QmlVersionInfo &versionInfo) { const QString module = type.module().isEmpty() ? versionInfo.pluginImportUri : type.module(); @@ -382,12 +353,14 @@ public: type.version().hasMinorVersion() ? type.version().minorVersion() : versionInfo.version.minorVersion()); - const QString versionedElement = type.elementName() - + QString::fromLatin1(" %1.%2").arg(version.majorVersion()).arg(version.minorVersion()); + const QByteArray versionedElement + = (type.elementName() + QString::fromLatin1(" %1.%2") + .arg(version.majorVersion()) + .arg(version.minorVersion())).toUtf8(); - return enquote((module == relocatableModuleUri) + return (module == relocatableModuleUri) ? versionedElement - : module + QLatin1Char('/') + versionedElement); + : module.toUtf8() + '/' + versionedElement; } void writeMetaContent(const QMetaObject *meta, KnownAttributes *knownAttributes = nullptr) @@ -399,30 +372,31 @@ public: for (int index = meta->methodOffset(); index < meta->methodCount(); ++index) { QMetaMethod method = meta->method(index); QByteArray signature = method.methodSignature(); - if (signature == QByteArrayLiteral("destroyed(QObject*)") - || signature == QByteArrayLiteral("destroyed()") - || signature == QByteArrayLiteral("deleteLater()")) + if (signature == "destroyed(QObject*)" + || signature == "destroyed()" + || signature == "deleteLater()") { continue; + } dump(method, implicitSignals, knownAttributes); } // and add toString(), destroy() and destroy(int) - if (!knownAttributes || !knownAttributes->knownMethod(QByteArray("toString"), 0, QTypeRevision::zero())) { - qml->writeStartObject(QLatin1String("Method")); - qml->writeScriptBinding(QLatin1String("name"), enquote(QLatin1String("toString"))); + if (!knownAttributes || !knownAttributes->knownMethod("toString", 0, QTypeRevision::zero())) { + qml->writeStartObject("Method"); + qml->writeStringBinding("name", QLatin1String("toString")); qml->writeEndObject(); } - if (!knownAttributes || !knownAttributes->knownMethod(QByteArray("destroy"), 0, QTypeRevision::zero())) { - qml->writeStartObject(QLatin1String("Method")); - qml->writeScriptBinding(QLatin1String("name"), enquote(QLatin1String("destroy"))); + if (!knownAttributes || !knownAttributes->knownMethod("destroy", 0, QTypeRevision::zero())) { + qml->writeStartObject("Method"); + qml->writeStringBinding("name", QLatin1String("destroy")); qml->writeEndObject(); } - if (!knownAttributes || !knownAttributes->knownMethod(QByteArray("destroy"), 1, QTypeRevision::zero())) { - qml->writeStartObject(QLatin1String("Method")); - qml->writeScriptBinding(QLatin1String("name"), enquote(QLatin1String("destroy"))); - qml->writeStartObject(QLatin1String("Parameter")); - qml->writeScriptBinding(QLatin1String("name"), enquote(QLatin1String("delay"))); - qml->writeScriptBinding(QLatin1String("type"), enquote(QLatin1String("int"))); + if (!knownAttributes || !knownAttributes->knownMethod("destroy", 1, QTypeRevision::zero())) { + qml->writeStartObject("Method"); + qml->writeStringBinding("name", QLatin1String("destroy")); + qml->writeStartObject("Parameter"); + qml->writeStringBinding("name", QLatin1String("delay")); + qml->writeStringBinding("type", QLatin1String("int")); qml->writeEndObject(); qml->writeEndObject(); } @@ -432,12 +406,12 @@ public: } } - QString getPrototypeNameForCompositeType( + QByteArray getPrototypeNameForCompositeType( const QMetaObject *metaObject, QList<const QMetaObject *> *objectsToMerge, const QmlVersionInfo &versionInfo) { auto ty = QQmlMetaType::qmlType(metaObject); - QString prototypeName; + QByteArray prototypeName; if (matchingImportUri(ty, versionInfo)) { // dynamic meta objects can break things badly // but extended types are usually fine @@ -488,40 +462,41 @@ public: QList<const QMetaObject *> objectsToMerge; KnownAttributes knownAttributes; // Get C++ base class name for the composite type - QString prototypeName = getPrototypeNameForCompositeType(mainMeta, &objectsToMerge, - versionInfo); - qml->writeScriptBinding(QLatin1String("prototype"), enquote(prototypeName)); + QByteArray prototypeName = getPrototypeNameForCompositeType( + mainMeta, &objectsToMerge, versionInfo); + qml->writeStringBinding("prototype", QUtf8StringView(prototypeName)); - QString qmlTyName = compositeType.qmlTypeName(); - const QString exportString = getExportString(compositeType, versionInfo); + const QByteArray 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->writeStringBinding("name", QUtf8StringView(exportString)); - qml->writeArrayBinding(QLatin1String("exports"), QStringList() << exportString); + qml->writeStringListBinding( + "exports", QList<QAnyStringView> { QUtf8StringView(exportString) }); // TODO: shouldn't this be metaObjectRevision().value<quint16>() // rather than version().minorVersion() - qml->writeArrayBinding(QLatin1String("exportMetaObjectRevisions"), QStringList() - << QString::number(compositeType.version().minorVersion())); + qml->writeArrayBinding( + "exportMetaObjectRevisions", + QByteArrayList() << QByteArray::number(compositeType.version().minorVersion())); - qml->writeBooleanBinding(QLatin1String("isComposite"), true); + qml->writeBooleanBinding("isComposite", true); if (compositeType.isSingleton()) { - qml->writeBooleanBinding(QLatin1String("isCreatable"), false); - qml->writeBooleanBinding(QLatin1String("isSingleton"), true); + qml->writeBooleanBinding("isCreatable", false); + qml->writeBooleanBinding("isSingleton", true); } for (int index = mainMeta->classInfoCount() - 1 ; index >= 0 ; --index) { QMetaClassInfo classInfo = mainMeta->classInfo(index); - if (QLatin1String(classInfo.name()) == QLatin1String("DefaultProperty")) { - qml->writeScriptBinding(QLatin1String("defaultProperty"), enquote(QLatin1String(classInfo.value()))); + if (QUtf8StringView(classInfo.name()) == QUtf8StringView("DefaultProperty")) { + qml->writeStringBinding("defaultProperty", QUtf8StringView(classInfo.value())); break; } } - for (const QMetaObject *meta : qAsConst(objectsToMerge)) { + for (const QMetaObject *meta : std::as_const(objectsToMerge)) { for (int index = meta->enumeratorOffset(); index < meta->enumeratorCount(); ++index) dump(meta->enumerator(index)); @@ -531,22 +506,29 @@ public: qml->writeEndObject(); } - QString getDefaultProperty(const QMetaObject *meta) + QByteArray getDefaultProperty(const QMetaObject *meta) { for (int index = meta->classInfoCount() - 1; index >= 0; --index) { QMetaClassInfo classInfo = meta->classInfo(index); if (QLatin1String(classInfo.name()) == QLatin1String("DefaultProperty")) { - return QLatin1String(classInfo.value()); + return QByteArray(classInfo.value()); } } - return QString(); + return QByteArray(); } struct QmlTypeInfo { QmlTypeInfo() {} - QmlTypeInfo(const QString &exportString, QTypeRevision revision, const QMetaObject *extendedObject, QByteArray attachedTypeId) - : exportString(exportString), revision(revision), extendedObject(extendedObject), attachedTypeId(attachedTypeId) {} - QString exportString; + QmlTypeInfo( + const QByteArray &exportString, QTypeRevision revision, + const QMetaObject *extendedObject, QByteArray attachedTypeId) + : exportString(exportString) + , revision(revision) + , extendedObject(extendedObject) + , attachedTypeId(attachedTypeId) + {} + + QByteArray exportString; QTypeRevision revision = QTypeRevision::zero(); const QMetaObject *extendedObject = nullptr; QByteArray attachedTypeId; @@ -557,11 +539,12 @@ public: qml->writeStartObject("Component"); QByteArray id = convertToId(meta); - qml->writeScriptBinding(QLatin1String("name"), enquote(id)); + qml->writeStringBinding("name", QUtf8StringView(id)); // collect type information QVector<QmlTypeInfo> typeInfo; - for (QQmlType type : qmlTypesByCppName.value(meta->className())) { + const auto types = qmlTypesByCppName.value(meta->className()); + for (const QQmlType &type : types) { const QMetaObject *extendedObject = type.extensionFunction() ? type.metaObject() : nullptr; QByteArray attachedTypeId; if (const QMetaObject *attachedType = type.attachedPropertiesType(engine)) { @@ -570,7 +553,8 @@ public: if (attachedType != meta) attachedTypeId = convertToId(attachedType); } - const QString exportString = getExportString(type, { QString(), QTypeRevision(), false }); + const QByteArray exportString = getExportString( + type, { QString(), QTypeRevision(), false }); QTypeRevision metaObjectRevision = type.metaObjectRevision(); if (extendedObject) { // emulate custom metaobjectrevision out of import @@ -588,7 +572,7 @@ public: // determine default property // TODO: support revisioning of default property - QString defaultProperty = getDefaultProperty(meta); + QByteArray defaultProperty = getDefaultProperty(meta); if (defaultProperty.isEmpty()) { for (const QmlTypeInfo &iter : typeInfo) { if (iter.extendedObject) { @@ -599,31 +583,33 @@ public: } } if (!defaultProperty.isEmpty()) - qml->writeScriptBinding(QLatin1String("defaultProperty"), enquote(defaultProperty)); + qml->writeStringBinding("defaultProperty", defaultProperty); if (meta->superClass()) - qml->writeScriptBinding(QLatin1String("prototype"), enquote(convertToId(meta->superClass()))); + qml->writeStringBinding("prototype", convertToId(meta->superClass())); if (!typeInfo.isEmpty()) { - QMap<QString, QString> exports; // sort exports - for (const QmlTypeInfo &iter : typeInfo) - exports.insert(iter.exportString, QString::number(iter.revision.toEncodedVersion<quint16>())); + QMap<QAnyStringView, QByteArray> exports; // sort exports + for (const QmlTypeInfo &iter : typeInfo) { + exports.insert( + QUtf8StringView(iter.exportString), + QByteArray::number(iter.revision.toEncodedVersion<quint16>())); + } - QStringList exportStrings = exports.keys(); - QStringList metaObjectRevisions = exports.values(); - qml->writeArrayBinding(QLatin1String("exports"), exportStrings); + QByteArrayList metaObjectRevisions = exports.values(); + qml->writeStringListBinding("exports", exports.keys()); if (isUncreatable) - qml->writeBooleanBinding(QLatin1String("isCreatable"), false); + qml->writeBooleanBinding("isCreatable", false); if (isSingleton) - qml->writeBooleanBinding(QLatin1String("isSingleton"), true); + qml->writeBooleanBinding("isSingleton", true); - qml->writeArrayBinding(QLatin1String("exportMetaObjectRevisions"), metaObjectRevisions); + qml->writeArrayBinding("exportMetaObjectRevisions", metaObjectRevisions); for (const QmlTypeInfo &iter : typeInfo) { if (!iter.attachedTypeId.isEmpty()) { - qml->writeScriptBinding(QLatin1String("attachedType"), enquote(iter.attachedTypeId)); + qml->writeStringBinding("attachedType", iter.attachedTypeId); break; } } @@ -635,7 +621,7 @@ public: writeMetaContent(meta); // dump properties from extended metaobjects last - for (auto iter : typeInfo) { + for (const auto &iter : typeInfo) { if (iter.extendedObject) dumpMetaProperties(iter.extendedObject, iter.revision); } @@ -654,11 +640,11 @@ private: if (typeName->endsWith('*')) { *isPointer = true; - typeName->truncate(typeName->length() - 1); + typeName->truncate(typeName->size() - 1); removePointerAndList(typeName, isList, isPointer); } else if (typeName->startsWith(declListPrefix)) { *isList = true; - typeName->truncate(typeName->length() - 1); // get rid of the suffix '>' + typeName->truncate(typeName->size() - 1); // get rid of the suffix '>' *typeName = typeName->mid(declListPrefix.size()); removePointerAndList(typeName, isList, isPointer); } @@ -671,13 +657,13 @@ private: bool isList = false, isPointer = false; removePointerAndList(&typeName, &isList, &isPointer); - qml->writeScriptBinding(QLatin1String("type"), enquote(typeName)); + qml->writeStringBinding("type", QUtf8StringView(typeName)); if (isList) - qml->writeScriptBinding(QLatin1String("isList"), QLatin1String("true")); + qml->writeBooleanBinding("isList", true); if (!isWritable) - qml->writeScriptBinding(QLatin1String("isReadonly"), QLatin1String("true")); + qml->writeBooleanBinding("isReadonly", true); if (isPointer) - qml->writeScriptBinding(QLatin1String("isPointer"), QLatin1String("true")); + qml->writeBooleanBinding("isPointer", true); } void dump(const QMetaProperty &prop, QTypeRevision metaRevision = QTypeRevision(), @@ -691,9 +677,9 @@ private: if (knownAttributes && knownAttributes->knownProperty(propName, revision)) return; qml->writeStartObject("Property"); - qml->writeScriptBinding(QLatin1String("name"), enquote(QString::fromUtf8(prop.name()))); + qml->writeStringBinding("name", QUtf8StringView(prop.name())); if (revision != QTypeRevision::zero()) - qml->writeScriptBinding(QLatin1String("revision"), QString::number(revision.toEncodedVersion<quint16>())); + qml->writeNumberBinding("revision", revision.toEncodedVersion<quint16>()); writeTypeProperties(prop.typeName(), prop.isWritable()); qml->writeEndObject(); @@ -706,10 +692,13 @@ private: for (int index = meta->propertyOffset(); index < meta->propertyCount(); ++index) { const QMetaProperty &property = meta->property(index); dump(property, metaRevision, knownAttributes); + const QByteArray changedSignalName = + QQmlSignalNames::propertyNameToChangedSignalName(property.name()); if (knownAttributes) - knownAttributes->knownMethod(QByteArray(property.name()).append("Changed"), - 0, QTypeRevision::fromEncodedVersion(property.revision())); - implicitSignals.insert(QString("%1Changed").arg(QString::fromUtf8(property.name()))); + knownAttributes->knownMethod( + changedSignalName, 0, + QTypeRevision::fromEncodedVersion(property.revision())); + implicitSignals.insert(changedSignalName); } return implicitSignals; } @@ -725,13 +714,13 @@ private: } QByteArray name = meth.name(); - const QString typeName = convertToId(meth.typeName()); + const QByteArray typeName = convertToId(meth.typeName()); if (implicitSignals.contains(name) && !meth.revision() && meth.methodType() == QMetaMethod::Signal && meth.parameterNames().isEmpty() - && typeName == QLatin1String("void")) { + && typeName == "void") { // don't mention implicit signals return; } @@ -740,24 +729,24 @@ private: if (knownAttributes && knownAttributes->knownMethod(name, meth.parameterNames().size(), revision)) return; if (meth.methodType() == QMetaMethod::Signal) - qml->writeStartObject(QLatin1String("Signal")); + qml->writeStartObject("Signal"); else - qml->writeStartObject(QLatin1String("Method")); + qml->writeStartObject("Method"); - qml->writeScriptBinding(QLatin1String("name"), enquote(name)); + qml->writeStringBinding("name", QUtf8StringView(name)); if (revision != QTypeRevision::zero()) - qml->writeScriptBinding(QLatin1String("revision"), QString::number(revision.toEncodedVersion<quint16>())); + qml->writeNumberBinding("revision", revision.toEncodedVersion<quint16>()); - if (typeName != QLatin1String("void")) - qml->writeScriptBinding(QLatin1String("type"), enquote(typeName)); + if (typeName != "void") + qml->writeStringBinding("type", QUtf8StringView(typeName)); for (int i = 0; i < meth.parameterTypes().size(); ++i) { QByteArray argName = meth.parameterNames().at(i); - qml->writeStartObject(QLatin1String("Parameter")); - if (! argName.isEmpty()) - qml->writeScriptBinding(QLatin1String("name"), enquote(argName)); + qml->writeStartObject("Parameter"); + if (!argName.isEmpty()) + qml->writeStringBinding("name", QUtf8StringView(argName)); writeTypeProperties(meth.parameterTypes().at(i), true); qml->writeEndObject(); } @@ -767,17 +756,16 @@ private: void dump(const QMetaEnum &e) { - qml->writeStartObject(QLatin1String("Enum")); - qml->writeScriptBinding(QLatin1String("name"), enquote(QString::fromUtf8(e.name()))); + qml->writeStartObject("Enum"); + qml->writeStringBinding("name", QUtf8StringView(e.name())); - QList<QPair<QString, QString> > namesValues; + QList<QPair<QAnyStringView, int>> namesValues; const int keyCount = e.keyCount(); namesValues.reserve(keyCount); - for (int index = 0; index < keyCount; ++index) { - namesValues.append(qMakePair(enquote(QString::fromUtf8(e.key(index))), QString::number(e.value(index)))); - } + for (int index = 0; index < keyCount; ++index) + namesValues.append(qMakePair(QUtf8StringView(e.key(index)), e.value(index))); - qml->writeScriptObjectLiteralBinding(QLatin1String("values"), namesValues); + qml->writeEnumObjectLiteralBinding("values", namesValues); qml->writeEndObject(); } }; @@ -835,7 +823,7 @@ static bool readDependenciesData(QString dependenciesFile, const QByteArray &fil continue; if (name.contains(QLatin1String("Private"), Qt::CaseInsensitive)) { if (verbose) - std::cerr << "skipping private dependecy " + std::cerr << "skipping private dependency " << qPrintable( name ) << " " << qPrintable(version) << std::endl; continue; } @@ -910,7 +898,7 @@ static bool getDependencies(const QQmlEngine &engine, const QString &pluginImpor if (!importScanner.waitForFinished()) { std::cerr << "failure to start " << qPrintable(command); - for (const QString &arg : qAsConst(commandArgs)) + for (const QString &arg : std::as_const(commandArgs)) std::cerr << ' ' << qPrintable(arg); std::cerr << std::endl; return false; @@ -945,18 +933,18 @@ bool dependencyBetter(const QString &lhs, const QString &rhs) if (leftModule > rightModule) return false; - if (leftSegments.length() == 1) + if (leftSegments.size() == 1) return false; - if (rightSegments.length() == 1) + if (rightSegments.size() == 1) return true; const QStringList leftVersion = leftSegments.at(1).split(QLatin1Char('.')); const QStringList rightVersion = rightSegments.at(1).split(QLatin1Char('.')); auto compareSegment = [&](int segmentIndex) { - if (leftVersion.length() <= segmentIndex) - return rightVersion.length() > segmentIndex ? 1 : 0; - if (rightVersion.length() <= segmentIndex) + if (leftVersion.size() <= segmentIndex) + return rightVersion.size() > segmentIndex ? 1 : 0; + if (rightVersion.size() <= segmentIndex) return -1; bool leftOk = false; @@ -991,19 +979,9 @@ void compactDependencies(QStringList *dependencies) } } -inline std::wostream &operator<<(std::wostream &str, const QString &s) -{ -#ifdef Q_OS_WIN - str << reinterpret_cast<const wchar_t *>(s.utf16()); -#else - str << s.toStdWString(); -#endif - return str; -} - void printDebugMessage(QtMsgType, const QMessageLogContext &, const QString &msg) { - std::wcerr << msg << std::endl; + std::cerr << msg.toStdString() << std::endl; // In case of QtFatalMsg the calling code will abort() when appropriate. } @@ -1192,23 +1170,26 @@ int main(int argc, char *argv[]) QDir cur = QDir::current(); cur.cd(pluginImportPath); pluginImportPath = cur.canonicalPath(); - QDir::setCurrent(pluginImportPath); + if (!QDir::setCurrent(pluginImportPath)) { + std::cerr << "Cannot set current directory to import path " + << qPrintable(pluginImportPath) << std::endl; + } engine.addImportPath(pluginImportPath); } // Merge file. QStringList mergeDependencies; - QString mergeComponents; + QByteArray mergeComponents; if (!mergeFile.isEmpty()) { const QStringList merge = readQmlTypes(mergeFile); if (!merge.isEmpty()) { - QRegularExpression re("(\\w+\\.*\\w*\\s*\\d+\\.\\d+)"); + static const QRegularExpression re("(\\w+\\.*\\w*\\s*\\d+\\.\\d+)"); QRegularExpressionMatchIterator i = re.globalMatch(merge[1]); while (i.hasNext()) { QRegularExpressionMatch m = i.next(); mergeDependencies << m.captured(1); } - mergeComponents = merge [2]; + mergeComponents = merge[2].toUtf8(); } } @@ -1233,7 +1214,7 @@ int main(int argc, char *argv[]) // load the QtQml builtins and the dependencies { QByteArray code(qtQmlImportString.toUtf8()); - for (const QString &moduleToImport : qAsConst(dependencies)) { + for (const QString &moduleToImport : std::as_const(dependencies)) { code.append("\nimport "); code.append(moduleToImport.toUtf8()); } @@ -1297,7 +1278,7 @@ int main(int argc, char *argv[]) QString::number(qtObjectType.version().minorVersion())).toUtf8(); } // avoid importing dependencies? - for (const QString &moduleToImport : qAsConst(dependencies)) { + for (const QString &moduleToImport : std::as_const(dependencies)) { importCode.append("\nimport "); importCode.append(moduleToImport.toUtf8()); } @@ -1334,7 +1315,7 @@ int main(int argc, char *argv[]) it->erase(std::unique(it->begin(), it->end()), it->end()); } - for (const QMetaObject *mo : qAsConst(candidates)) { + for (const QMetaObject *mo : std::as_const(candidates)) { if (mo->className() != QLatin1String("Qt")) metas.insert(mo); } @@ -1348,27 +1329,31 @@ int main(int argc, char *argv[]) QQmlJSStreamWriter qml(&bytes); qml.writeStartDocument(); - qml.writeLibraryImport(QLatin1String("QtQuick.tooling"), 1, 2); - qml.write(QString("\n" + qml.writeLibraryImport("QtQuick.tooling", 1, 2); + qml.write("\n" "// This file describes the plugin-supplied types contained in the library.\n" "// It is used for QML tooling purposes only.\n" "//\n" "// This file was auto-generated by:\n" - "// '%1 %2'\n" + "// '"); + qml.write(QFileInfo(args.at(0)).baseName().toUtf8()); + qml.write(" "); + qml.write(args.mid(1).join(QLatin1Char(' ')).toUtf8()); + qml.write("'\n" "//\n" "// qmlplugindump is deprecated! You should use qmltyperegistrar instead.\n" - "\n").arg(QFileInfo(args.at(0)).baseName(), args.mid(1).join(QLatin1Char(' ')))); + "\n"); qml.writeStartObject("Module"); // put the metaobjects into a map so they are always dumped in the same order QMap<QString, const QMetaObject *> nameToMeta; - for (const QMetaObject *meta : qAsConst(metas)) + for (const QMetaObject *meta : std::as_const(metas)) nameToMeta.insert(convertToId(meta), meta); Dumper dumper(&qml); if (relocatable) dumper.setRelocatableModuleUri(pluginImportUri); - for (const QMetaObject *meta : qAsConst(nameToMeta)) { + for (const QMetaObject *meta : std::as_const(nameToMeta)) { dumper.dump(QQmlEnginePrivate::get(&engine), meta, uncreatableMetas.contains(meta), singletonMetas.contains(meta)); } diff --git a/tools/qmlplugindump/qmltypereader.cpp b/tools/qmlplugindump/qmltypereader.cpp index 06f6086d4a..ade3909253 100644 --- a/tools/qmlplugindump/qmltypereader.cpp +++ b/tools/qmlplugindump/qmltypereader.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2017 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$ -** -****************************************************************************/ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "qmltypereader.h" diff --git a/tools/qmlplugindump/qmltypereader.h b/tools/qmlplugindump/qmltypereader.h index bb31cf43e1..f1f232b9fd 100644 --- a/tools/qmlplugindump/qmltypereader.h +++ b/tools/qmlplugindump/qmltypereader.h @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2017 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$ -** -****************************************************************************/ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #ifndef QMLTYPEREADER_H #define QMLTYPEREADER_H diff --git a/tools/qmlpreview/.prev_CMakeLists.txt b/tools/qmlpreview/.prev_CMakeLists.txt deleted file mode 100644 index 61efd03729..0000000000 --- a/tools/qmlpreview/.prev_CMakeLists.txt +++ /dev/null @@ -1,20 +0,0 @@ -# Generated from qmlpreview.pro. - -##################################################################### -## qmlpreview Tool: -##################################################################### - -qt_get_tool_target_name(target_name qmlpreview) -qt_internal_add_tool(${target_name} - TARGET_DESCRIPTION "QML Preview" - SOURCES - main.cpp - qmlpreviewapplication.cpp qmlpreviewapplication.h - qmlpreviewfilesystemwatcher.cpp qmlpreviewfilesystemwatcher.h - PUBLIC_LIBRARIES - Qt::Network - Qt::QmlDebugPrivate -) - -#### Keys ignored in scope 1:.:.:qmlpreview.pro:<TRUE>: -# QMAKE_TARGET_DESCRIPTION = "QML" "Preview" diff --git a/tools/qmlpreview/CMakeLists.txt b/tools/qmlpreview/CMakeLists.txt index 4c347a404e..eb39cbd8cd 100644 --- a/tools/qmlpreview/CMakeLists.txt +++ b/tools/qmlpreview/CMakeLists.txt @@ -1,21 +1,25 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + # Generated from qmlpreview.pro. ##################################################################### -## qmlpreview Tool: +## qmlpreview App: ##################################################################### -qt_get_tool_target_name(target_name qmlpreview) -qt_internal_add_tool(${target_name} +qt_internal_add_app(qmlpreview TARGET_DESCRIPTION "QML Preview" - TOOLS_TARGET Qml # special case SOURCES main.cpp qmlpreviewapplication.cpp qmlpreviewapplication.h qmlpreviewfilesystemwatcher.cpp qmlpreviewfilesystemwatcher.h - PUBLIC_LIBRARIES + LIBRARIES Qt::Network Qt::QmlDebugPrivate ) +qt_internal_return_unless_building_tools() + +set_target_properties(qmlpreview PROPERTIES WIN32_EXECUTABLE FALSE) #### Keys ignored in scope 1:.:.:qmlpreview.pro:<TRUE>: # QMAKE_TARGET_DESCRIPTION = "QML" "Preview" diff --git a/tools/qmlpreview/main.cpp b/tools/qmlpreview/main.cpp index c7a32da258..48c632f19e 100644 --- a/tools/qmlpreview/main.cpp +++ b/tools/qmlpreview/main.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2018 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$ -** -****************************************************************************/ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "qmlpreviewapplication.h" diff --git a/tools/qmlpreview/qmlpreviewapplication.cpp b/tools/qmlpreview/qmlpreviewapplication.cpp index 2568425573..e925ef036e 100644 --- a/tools/qmlpreview/qmlpreviewapplication.cpp +++ b/tools/qmlpreview/qmlpreviewapplication.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2018 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtQml module 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$ -** -****************************************************************************/ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "qmlpreviewapplication.h" @@ -39,6 +14,7 @@ #include <QtCore/QCommandLineParser> #include <QtCore/QTemporaryFile> #include <QtCore/QUrl> +#include <QtCore/QLibraryInfo> QmlPreviewApplication::QmlPreviewApplication(int &argc, char **argv) : QCoreApplication(argc, argv), diff --git a/tools/qmlpreview/qmlpreviewapplication.h b/tools/qmlpreview/qmlpreviewapplication.h index 51a70cbac3..7e71c2dac5 100644 --- a/tools/qmlpreview/qmlpreviewapplication.h +++ b/tools/qmlpreview/qmlpreviewapplication.h @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2018 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtQml module 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$ -** -****************************************************************************/ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #ifndef QMLPREVIEWAPPLICATION_H #define QMLPREVIEWAPPLICATION_H diff --git a/tools/qmlpreview/qmlpreviewfilesystemwatcher.cpp b/tools/qmlpreview/qmlpreviewfilesystemwatcher.cpp index 78a6ccead3..4db2c03b20 100644 --- a/tools/qmlpreview/qmlpreviewfilesystemwatcher.cpp +++ b/tools/qmlpreview/qmlpreviewfilesystemwatcher.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2018 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtQml module 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$ -** -****************************************************************************/ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "qmlpreviewfilesystemwatcher.h" @@ -144,7 +119,7 @@ void QmlPreviewFileSystemWatcher::onDirectoryChanged(const QString &path) toReadd.removeOne(rejected); // If we've successfully added the file, that means it was deleted and replaced. - for (const QString &reAdded : qAsConst(toReadd)) + for (const QString &reAdded : std::as_const(toReadd)) emit fileChanged(reAdded); } } diff --git a/tools/qmlpreview/qmlpreviewfilesystemwatcher.h b/tools/qmlpreview/qmlpreviewfilesystemwatcher.h index a7ead641d8..6304405b9d 100644 --- a/tools/qmlpreview/qmlpreviewfilesystemwatcher.h +++ b/tools/qmlpreview/qmlpreviewfilesystemwatcher.h @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2018 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtQml module 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$ -** -****************************************************************************/ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #ifndef QMLPREVIEWFILESYSTEMWATCHER_H #define QMLPREVIEWFILESYSTEMWATCHER_H @@ -48,7 +23,7 @@ public: void removeDirectory(const QString &file); bool watchesDirectory(const QString &file) const; -signals: +Q_SIGNALS: void fileChanged(const QString &path); void directoryChanged(const QString &path); diff --git a/tools/qmlprofiler/.prev_CMakeLists.txt b/tools/qmlprofiler/.prev_CMakeLists.txt deleted file mode 100644 index 51418f62dc..0000000000 --- a/tools/qmlprofiler/.prev_CMakeLists.txt +++ /dev/null @@ -1,23 +0,0 @@ -# Generated from qmlprofiler.pro. - -##################################################################### -## qmlprofiler Tool: -##################################################################### - -qt_get_tool_target_name(target_name qmlprofiler) -qt_internal_add_tool(${target_name} - TARGET_DESCRIPTION "QML Profiler" - SOURCES - commandlistener.cpp commandlistener.h - constants.h - main.cpp - qmlprofilerapplication.cpp qmlprofilerapplication.h - qmlprofilerclient.cpp qmlprofilerclient.h - qmlprofilerdata.cpp qmlprofilerdata.h - PUBLIC_LIBRARIES - Qt::Network - Qt::QmlDebugPrivate -) - -#### Keys ignored in scope 1:.:.:qmlprofiler.pro:<TRUE>: -# QMAKE_TARGET_DESCRIPTION = "QML" "Profiler" diff --git a/tools/qmlprofiler/CMakeLists.txt b/tools/qmlprofiler/CMakeLists.txt index c2e5c111ae..72a2487a47 100644 --- a/tools/qmlprofiler/CMakeLists.txt +++ b/tools/qmlprofiler/CMakeLists.txt @@ -1,3 +1,6 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + # Generated from qmlprofiler.pro. ##################################################################### @@ -15,10 +18,11 @@ qt_internal_add_tool(${target_name} qmlprofilerapplication.cpp qmlprofilerapplication.h qmlprofilerclient.cpp qmlprofilerclient.h qmlprofilerdata.cpp qmlprofilerdata.h - PUBLIC_LIBRARIES + LIBRARIES Qt::Network Qt::QmlDebugPrivate ) +qt_internal_return_unless_building_tools() #### Keys ignored in scope 1:.:.:qmlprofiler.pro:<TRUE>: # QMAKE_TARGET_DESCRIPTION = "QML" "Profiler" diff --git a/tools/qmlprofiler/commandlistener.cpp b/tools/qmlprofiler/commandlistener.cpp index 079ea9b3bd..3d1fc2d311 100644 --- a/tools/qmlprofiler/commandlistener.cpp +++ b/tools/qmlprofiler/commandlistener.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtQml module 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$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "commandlistener.h" #include "constants.h" diff --git a/tools/qmlprofiler/commandlistener.h b/tools/qmlprofiler/commandlistener.h index 2a994bf449..65ba3682bd 100644 --- a/tools/qmlprofiler/commandlistener.h +++ b/tools/qmlprofiler/commandlistener.h @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtQml module 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$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #ifndef COMMANDLISTENER_H #define COMMANDLISTENER_H @@ -36,7 +11,7 @@ class CommandListener : public QObject { public: void readCommand(); -signals: +Q_SIGNALS: void command(const QString &command); }; diff --git a/tools/qmlprofiler/constants.h b/tools/qmlprofiler/constants.h index 8c3e16a8a7..6fdcbd8c82 100644 --- a/tools/qmlprofiler/constants.h +++ b/tools/qmlprofiler/constants.h @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtQml module 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$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #ifndef CONSTANTS_H #define CONSTANTS_H diff --git a/tools/qmlprofiler/main.cpp b/tools/qmlprofiler/main.cpp index c7cb979ff8..e89eecbef2 100644 --- a/tools/qmlprofiler/main.cpp +++ b/tools/qmlprofiler/main.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 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$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "commandlistener.h" #include "qmlprofilerapplication.h" diff --git a/tools/qmlprofiler/qmlprofilerapplication.cpp b/tools/qmlprofiler/qmlprofilerapplication.cpp index 4d97efafb4..9447333700 100644 --- a/tools/qmlprofiler/qmlprofilerapplication.cpp +++ b/tools/qmlprofiler/qmlprofilerapplication.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtQml module 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$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "qmlprofilerapplication.h" #include "constants.h" @@ -36,6 +11,7 @@ #include <QtCore/QDebug> #include <QtCore/QCommandLineParser> #include <QtCore/QTemporaryFile> +#include <QtCore/QLibraryInfo> #include <iostream> @@ -408,7 +384,7 @@ void QmlProfilerApplication::userCommand(const QString &command) } else if (m_profilerData->isEmpty()) { prompt(tr("No data was recorded so far.")); } else { - m_interactiveOutputFile = args.length() > 0 ? args.at(0).toString() : m_outputFile; + m_interactiveOutputFile = args.size() > 0 ? args.at(0).toString() : m_outputFile; if (checkOutputFile(REQUEST_OUTPUT_FILE)) output(); } @@ -425,7 +401,7 @@ void QmlProfilerApplication::userCommand(const QString &command) if (!m_recording && m_profilerData->isEmpty()) { prompt(tr("No data was recorded so far.")); } else { - m_interactiveOutputFile = args.length() > 0 ? args.at(0).toString() : m_outputFile; + m_interactiveOutputFile = args.size() > 0 ? args.at(0).toString() : m_outputFile; if (checkOutputFile(REQUEST_FLUSH_FILE)) flush(); } diff --git a/tools/qmlprofiler/qmlprofilerapplication.h b/tools/qmlprofiler/qmlprofilerapplication.h index e069659af3..b02faa7c9d 100644 --- a/tools/qmlprofiler/qmlprofilerapplication.h +++ b/tools/qmlprofiler/qmlprofilerapplication.h @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtQml module 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$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #ifndef QMLPROFILERAPPLICATION_H #define QMLPROFILERAPPLICATION_H @@ -62,7 +37,7 @@ public: void notifyTraceStarted(); void outputData(); -signals: +Q_SIGNALS: void readyForCommand(); private: diff --git a/tools/qmlprofiler/qmlprofilerclient.cpp b/tools/qmlprofiler/qmlprofilerclient.cpp index f6cc6f39fe..e8c565261c 100644 --- a/tools/qmlprofiler/qmlprofilerclient.cpp +++ b/tools/qmlprofiler/qmlprofilerclient.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtQml module 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$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "qmlprofilerclient.h" #include "qmlprofilerdata.h" diff --git a/tools/qmlprofiler/qmlprofilerclient.h b/tools/qmlprofiler/qmlprofilerclient.h index 7355688222..b6558869bc 100644 --- a/tools/qmlprofiler/qmlprofilerclient.h +++ b/tools/qmlprofiler/qmlprofilerclient.h @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtQml module 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$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #ifndef QMLPROFILERCLIENT_H #define QMLPROFILERCLIENT_H @@ -43,7 +18,7 @@ class QmlProfilerClient : public QQmlProfilerClient public: QmlProfilerClient(QQmlDebugConnection *connection, QmlProfilerData *data); -signals: +Q_SIGNALS: void enabledChanged(bool enabled); void error(const QString &error); diff --git a/tools/qmlprofiler/qmlprofilerdata.cpp b/tools/qmlprofiler/qmlprofilerdata.cpp index 8803170ff2..c9e917cc00 100644 --- a/tools/qmlprofiler/qmlprofilerdata.cpp +++ b/tools/qmlprofiler/qmlprofilerdata.cpp @@ -1,41 +1,14 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtQml module 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$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "qmlprofilerdata.h" -#include <QStringList> -#include <QUrl> -#include <QHash> -#include <QFile> -#include <QXmlStreamReader> -#include <QRegularExpression> -#include <QQueue> -#include <QStack> +#include <QtCore/qfile.h> +#include <QtCore/qqueue.h> +#include <QtCore/qregularexpression.h> +#include <QtCore/qurl.h> +#include <QtCore/qxmlstream.h> +#include <QtCore/qxpfunctional.h> #include <limits> @@ -236,7 +209,7 @@ void QmlProfilerData::computeQmlTime() qint64 level0Start = -1; int level = 0; - for (const QQmlProfilerEvent &event : qAsConst(d->events)) { + for (const QQmlProfilerEvent &event : std::as_const(d->events)) { const QQmlProfilerEventType &type = d->eventTypes.at(event.typeIndex()); if (type.message() != MaximumMessage) continue; @@ -273,7 +246,7 @@ bool compareStartTimes(const QQmlProfilerEvent &t1, const QQmlProfilerEvent &t2) void QmlProfilerData::sortStartTimes() { - if (d->events.count() < 2) + if (d->events.size() < 2) return; // assuming startTimes is partially sorted @@ -400,6 +373,132 @@ private: QXmlStreamWriter stream; }; +struct DataIterator +{ + DataIterator( + const QmlProfilerDataPrivate *d, + qxp::function_ref<void(const QQmlProfilerEvent &, qint64)> &&sendEvent) + : d(d) + , sendEvent(std::move(sendEvent)) + {} + + void run(); + +private: + void handleRangeEvent(const QQmlProfilerEvent &event, const QQmlProfilerEventType &type); + void sendPending(); + void endLevel0(); + + const QmlProfilerDataPrivate *d = nullptr; + const qxp::function_ref<void(const QQmlProfilerEvent &, qint64)> sendEvent; + + QQueue<QQmlProfilerEvent> pointEvents; + QList<QQmlProfilerEvent> rangeStarts[MaximumRangeType]; + QList<qint64> rangeEnds[MaximumRangeType]; + + int level = 0; +}; + +void DataIterator::handleRangeEvent( + const QQmlProfilerEvent &event, const QQmlProfilerEventType &type) +{ + QList<QQmlProfilerEvent> &starts = rangeStarts[type.rangeType()]; + switch (event.rangeStage()) { + case RangeStart: { + ++level; + starts.append(event); + break; + } + case RangeEnd: { + const qint64 invalidTimestamp = -1; + QList<qint64> &ends = rangeEnds[type.rangeType()]; + + // -1 because all valid timestamps are >= 0. + ends.resize(starts.size(), invalidTimestamp); + + qsizetype i = starts.size(); + while (ends[--i] != invalidTimestamp) {} + + Q_ASSERT(i >= 0); + Q_ASSERT(starts[i].timestamp() <= event.timestamp()); + + ends[i] = event.timestamp(); + if (--level == 0) + endLevel0(); + break; + } + default: + break; + } +} + +void DataIterator::sendPending() +{ + // Send all pending events in the order of their start times. + + qsizetype index[MaximumRangeType] = { 0, 0, 0, 0, 0, 0 }; + while (true) { + + // Find the range type with the minimum start time. + qsizetype minimum = MaximumRangeType; + qint64 minimumTime = std::numeric_limits<qint64>::max(); + for (qsizetype i = 0; i < MaximumRangeType; ++i) { + const QList<QQmlProfilerEvent> &starts = rangeStarts[i]; + if (starts.size() == index[i]) + continue; + const qint64 timestamp = starts[index[i]].timestamp(); + if (timestamp < minimumTime) { + minimumTime = timestamp; + minimum = i; + } + } + if (minimum == MaximumRangeType) + break; + + // Send all point events that happened before the range we've found. + while (!pointEvents.isEmpty() && pointEvents.front().timestamp() < minimumTime) + sendEvent(pointEvents.dequeue(), 0); + + // Send the range itself + sendEvent(rangeStarts[minimum][index[minimum]], + rangeEnds[minimum][index[minimum]] - minimumTime); + + // Bump the index so that we don't send the same range again + ++index[minimum]; + } +} + +void DataIterator::endLevel0() +{ + sendPending(); + for (qsizetype i = 0; i < MaximumRangeType; ++i) { + rangeStarts[i].clear(); + rangeEnds[i].clear(); + } +} + +void DataIterator::run() +{ + for (const QQmlProfilerEvent &event : std::as_const(d->events)) { + const QQmlProfilerEventType &type = d->eventTypes.at(event.typeIndex()); + if (type.rangeType() != MaximumRangeType) + handleRangeEvent(event, type); + else if (level == 0) + sendEvent(event, 0); + else + pointEvents.enqueue(event); + } + + for (qsizetype i = 0; i < MaximumRangeType; ++i) { + while (rangeEnds[i].size() < rangeStarts[i].size()) { + rangeEnds[i].append(d->traceEndTime); + --level; + } + } + + sendPending(); +} + bool QmlProfilerData::save(const QString &filename) { if (isEmpty()) { @@ -467,6 +566,7 @@ bool QmlProfilerData::save(const QString &filename) stream.writeStartElement("profilerDataModel"); auto sendEvent = [&](const QQmlProfilerEvent &event, qint64 duration = 0) { + Q_ASSERT(duration >= 0); const QQmlProfilerEventType &type = d->eventTypes.at(event.typeIndex()); stream.writeStartElement("range"); stream.writeAttribute("startTime", event.timestamp()); @@ -506,74 +606,7 @@ bool QmlProfilerData::save(const QString &filename) stream.writeEndElement(); }; - QQueue<QQmlProfilerEvent> pointEvents; - QQueue<QQmlProfilerEvent> rangeStarts[MaximumRangeType]; - QStack<qint64> rangeEnds[MaximumRangeType]; - int level = 0; - - auto sendPending = [&]() { - forever { - int minimum = MaximumRangeType; - qint64 minimumTime = std::numeric_limits<qint64>::max(); - for (int i = 0; i < MaximumRangeType; ++i) { - const QQueue<QQmlProfilerEvent> &starts = rangeStarts[i]; - if (starts.isEmpty()) - continue; - if (starts.head().timestamp() < minimumTime) { - minimumTime = starts.head().timestamp(); - minimum = i; - } - } - if (minimum == MaximumRangeType) - break; - - while (!pointEvents.isEmpty() && pointEvents.front().timestamp() < minimumTime) - sendEvent(pointEvents.dequeue()); - - sendEvent(rangeStarts[minimum].dequeue(), - rangeEnds[minimum].pop() - minimumTime); - } - }; - - for (const QQmlProfilerEvent &event : qAsConst(d->events)) { - const QQmlProfilerEventType &type = d->eventTypes.at(event.typeIndex()); - - if (type.rangeType() != MaximumRangeType) { - QQueue<QQmlProfilerEvent> &starts = rangeStarts[type.rangeType()]; - switch (event.rangeStage()) { - case RangeStart: { - ++level; - starts.enqueue(event); - break; - } - case RangeEnd: { - QStack<qint64> &ends = rangeEnds[type.rangeType()]; - if (starts.length() > ends.length()) { - ends.push(event.timestamp()); - if (--level == 0) - sendPending(); - } - break; - } - default: - break; - } - } else { - if (level == 0) - sendEvent(event); - else - pointEvents.enqueue(event); - } - } - - for (int i = 0; i < MaximumRangeType; ++i) { - while (rangeEnds[i].length() < rangeStarts[i].length()) { - rangeEnds[i].push(d->traceEndTime); - --level; - } - } - - sendPending(); + DataIterator(d, std::move(sendEvent)).run(); stream.writeEndElement(); // profilerDataModel @@ -622,7 +655,7 @@ void QmlProfilerData::setState(QmlProfilerData::State state) int QmlProfilerData::numLoadedEventTypes() const { - return d->eventTypes.length(); + return d->eventTypes.size(); } #include "moc_qmlprofilerdata.cpp" diff --git a/tools/qmlprofiler/qmlprofilerdata.h b/tools/qmlprofiler/qmlprofilerdata.h index 16ebc37f28..95b8869fac 100644 --- a/tools/qmlprofiler/qmlprofilerdata.h +++ b/tools/qmlprofiler/qmlprofilerdata.h @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtQml module 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$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #ifndef QMLPROFILERDATA_H #define QMLPROFILERDATA_H @@ -47,7 +22,7 @@ public: Done }; - explicit QmlProfilerData(QObject *parent = 0); + explicit QmlProfilerData(QObject *parent = nullptr); ~QmlProfilerData(); int numLoadedEventTypes() const override; @@ -70,7 +45,7 @@ public: void complete(); bool save(const QString &filename); -signals: +Q_SIGNALS: void error(QString); void stateChanged(); void dataReady(); diff --git a/tools/qmlscene/.prev_CMakeLists.txt b/tools/qmlscene/.prev_CMakeLists.txt deleted file mode 100644 index 5c29546d60..0000000000 --- a/tools/qmlscene/.prev_CMakeLists.txt +++ /dev/null @@ -1,37 +0,0 @@ -# Generated from qmlscene.pro. - -##################################################################### -## qmlscene Tool: -##################################################################### - -qt_get_tool_target_name(target_name qmlscene) -qt_internal_add_tool(${target_name} - TARGET_DESCRIPTION "QML Scene Viewer" - SOURCES - main.cpp - DEFINES - QML_RUNTIME_TESTING - PUBLIC_LIBRARIES - Qt::CorePrivate - Qt::Gui - Qt::GuiPrivate - Qt::Qml - Qt::Quick - Qt::QuickPrivate -) - -#### Keys ignored in scope 1:.:.:qmlscene.pro:<TRUE>: -# QMAKE_TARGET_DESCRIPTION = "QML" "Scene" "Viewer" - -## Scopes: -##################################################################### - -qt_internal_extend_target(${target_name} CONDITION TARGET Qt::Widgets - PUBLIC_LIBRARIES - Qt::Widgets -) - -qt_internal_extend_target(${target_name} CONDITION QT_FEATURE_qml_debug - DEFINES - QT_QML_DEBUG_NO_WARNING -) diff --git a/tools/qmlscene/CMakeLists.txt b/tools/qmlscene/CMakeLists.txt index 4566a60441..305420d471 100644 --- a/tools/qmlscene/CMakeLists.txt +++ b/tools/qmlscene/CMakeLists.txt @@ -1,18 +1,17 @@ -# Generated from qmlscene.pro. +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause ##################################################################### -## qmlscene Tool: +## qmlscene App: ##################################################################### -qt_get_tool_target_name(target_name qmlscene) -qt_internal_add_tool(${target_name} +qt_internal_add_app(qmlscene TARGET_DESCRIPTION "QML Scene Viewer" - TOOLS_TARGET Qml # special case SOURCES main.cpp DEFINES QML_RUNTIME_TESTING - PUBLIC_LIBRARIES + LIBRARIES Qt::CorePrivate Qt::Gui Qt::GuiPrivate @@ -21,18 +20,14 @@ qt_internal_add_tool(${target_name} Qt::QuickPrivate ) -#### Keys ignored in scope 1:.:.:qmlscene.pro:<TRUE>: -# QMAKE_TARGET_DESCRIPTION = "QML" "Scene" "Viewer" - -## Scopes: -##################################################################### +set_target_properties(qmlscene PROPERTIES WIN32_EXECUTABLE FALSE) -qt_internal_extend_target(${target_name} CONDITION TARGET Qt::Widgets +qt_internal_extend_target(qmlscene CONDITION TARGET Qt::Widgets PUBLIC_LIBRARIES Qt::Widgets ) -qt_internal_extend_target(${target_name} CONDITION QT_FEATURE_qml_debug +qt_internal_extend_target(qmlscene CONDITION QT_FEATURE_qml_debug DEFINES QT_QML_DEBUG_NO_WARNING ) diff --git a/tools/qmlscene/main.cpp b/tools/qmlscene/main.cpp index de00522d48..123844ebb3 100644 --- a/tools/qmlscene/main.cpp +++ b/tools/qmlscene/main.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 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$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include <QtCore/qabstractanimation.h> #include <QtCore/qdir.h> @@ -34,6 +9,7 @@ #include <QtCore/qscopedpointer.h> #include <QtCore/qtextstream.h> #include <QtCore/qregularexpression.h> +#include <QtCore/qloggingcategory.h> #include <QtGui/QGuiApplication> @@ -58,6 +34,8 @@ #include <QtCore/QTranslator> #include <QtCore/QLibraryInfo> +Q_LOGGING_CATEGORY(lcQmlsceneDeprecated, "qt.tools.qmlscene.deprecated") + #ifdef QML_RUNTIME_TESTING class RenderStatistics { @@ -108,7 +86,7 @@ void RenderStatistics::updateStats() void RenderStatistics::printTotalStats() { - int count = timePerFrame.count(); + int count = timePerFrame.size(); if (count == 0) return; @@ -333,7 +311,7 @@ static void loadDummyDataFiles(QQmlEngine &engine, const QString& directory) if (dummyData) { fprintf(stderr, "Loaded dummy data: %s\n", qPrintable(dir.filePath(qml))); - qml.truncate(qml.length()-4); + qml.truncate(qml.size()-4); engine.rootContext()->setContextProperty(qml, dummyData); dummyData->setParent(&engine); } @@ -359,7 +337,7 @@ static void usage() " ........ (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(" --software........................ Force use of software rendering (AA_UseSoftwareOpenGL)"); 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."); @@ -419,8 +397,7 @@ static QQuickWindow::TextRenderType parseTextRenderType(const QString &renderTyp usage(); - Q_UNREACHABLE(); - return QQuickWindow::QtTextRendering; + Q_UNREACHABLE_RETURN(QQuickWindow::QtTextRendering); } int main(int argc, char ** argv) @@ -431,6 +408,8 @@ int main(int argc, char ** argv) QStringList customSelectors; QStringList pluginPaths; + qCWarning(lcQmlsceneDeprecated()) << "Warning: qmlscene is deprecated and will be removed in a future version of Qt. Please use qml instead."; + // Parse arguments for application attributes to be applied before Q[Gui]Application creation. for (int i = 1; i < argc; ++i) { const char *arg = argv[i]; @@ -474,7 +453,7 @@ int main(int argc, char ** argv) } QSurfaceFormat::setDefaultFormat(surfaceFormat); - for (Qt::ApplicationAttribute a : qAsConst(options.applicationAttributes)) + for (Qt::ApplicationAttribute a : std::as_const(options.applicationAttributes)) QCoreApplication::setAttribute(a); QScopedPointer<QGuiApplication> app; #ifdef QT_WIDGETS_LIB diff --git a/tools/qmltc/CMakeLists.txt b/tools/qmltc/CMakeLists.txt new file mode 100644 index 0000000000..42e47e24da --- /dev/null +++ b/tools/qmltc/CMakeLists.txt @@ -0,0 +1,27 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +qt_get_tool_target_name(target_name qmltc) +qt_internal_add_tool(${target_name} + TARGET_DESCRIPTION "QML type compiler" + TOOLS_TARGET Qml + SOURCES + main.cpp + qmltccommandlineutils.h qmltccommandlineutils.cpp + qmltcoutputprimitives.h + qmltccodewriter.h qmltccodewriter.cpp + qmltcoutputir.h + qmltctyperesolver.h qmltctyperesolver.cpp + qmltcvisitor.h qmltcvisitor.cpp + qmltccompiler.h qmltccompiler.cpp + qmltccompilerpieces.h qmltccompilerpieces.cpp + qmltcpropertyutils.h + DEFINES + QT_NO_CAST_FROM_ASCII + QT_NO_CAST_TO_ASCII + LIBRARIES + Qt::Core + Qt::QmlPrivate + Qt::QmlCompilerPrivate +) +qt_internal_return_unless_building_tools() diff --git a/tools/qmltc/main.cpp b/tools/qmltc/main.cpp new file mode 100644 index 0000000000..a310c3d3c6 --- /dev/null +++ b/tools/qmltc/main.cpp @@ -0,0 +1,312 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qmltccommandlineutils.h" +#include "qmltcvisitor.h" +#include "qmltctyperesolver.h" + +#include "qmltccompiler.h" + +#include <private/qqmljscompiler_p.h> +#include <private/qqmljsresourcefilemapper_p.h> + +#include <QtCore/qcoreapplication.h> +#include <QtCore/qurl.h> +#include <QtCore/qhashfunctions.h> +#include <QtCore/qfileinfo.h> +#include <QtCore/qlibraryinfo.h> +#include <QtCore/qcommandlineparser.h> +#include <QtCore/qregularexpression.h> + +#include <QtQml/private/qqmljslexer_p.h> +#include <QtQml/private/qqmljsparser_p.h> +#include <QtQml/private/qqmljsengine_p.h> +#include <QtQml/private/qqmljsastvisitor_p.h> +#include <QtQml/private/qqmljsast_p.h> +#include <QtQml/private/qqmljsdiagnosticmessage_p.h> +#include <QtQmlCompiler/qqmlsa.h> +#include <QtQmlCompiler/private/qqmljsliteralbindingcheck_p.h> + +#include <cstdlib> // EXIT_SUCCESS, EXIT_FAILURE + +using namespace Qt::StringLiterals; + +void setupLogger(QQmlJSLogger &logger) // prepare logger to work with compiler +{ + for (const QQmlJS::LoggerCategory &category : logger.categories()) { + if (category.id() == qmlUnusedImports) + continue; + logger.setCategoryLevel(category.id(), QtCriticalMsg); + logger.setCategoryIgnored(category.id(), false); + } +} + +int main(int argc, char **argv) +{ + // Produce reliably the same output for the same input by disabling QHash's + // random seeding. + QHashSeed::setDeterministicGlobalSeed(); + QCoreApplication app(argc, argv); + QCoreApplication::setApplicationName(u"qmltc"_s); + QCoreApplication::setApplicationVersion(QStringLiteral(QT_VERSION_STR)); + + // command-line parsing: + QCommandLineParser parser; + parser.addHelpOption(); + parser.addVersionOption(); + + QCommandLineOption bareOption { + u"bare"_s, + QCoreApplication::translate( + "main", "Do not include default import directories. This may be used to run " + "qmltc on a project using a different Qt version.") + }; + parser.addOption(bareOption); + + QCommandLineOption importPathOption { + u"I"_s, QCoreApplication::translate("main", "Look for QML modules in specified directory"), + QCoreApplication::translate("main", "import directory") + }; + parser.addOption(importPathOption); + QCommandLineOption qmldirOption { + u"i"_s, QCoreApplication::translate("main", "Include extra qmldir files"), + QCoreApplication::translate("main", "qmldir file") + }; + parser.addOption(qmldirOption); + QCommandLineOption outputCppOption { + u"impl"_s, QCoreApplication::translate("main", "Generated C++ source file path"), + QCoreApplication::translate("main", "cpp path") + }; + parser.addOption(outputCppOption); + QCommandLineOption outputHOption { + u"header"_s, QCoreApplication::translate("main", "Generated C++ header file path"), + QCoreApplication::translate("main", "h path") + }; + parser.addOption(outputHOption); + QCommandLineOption resourceOption { + u"resource"_s, + QCoreApplication::translate( + "main", "Qt resource file that might later contain one of the compiled files"), + QCoreApplication::translate("main", "resource file name") + }; + parser.addOption(resourceOption); + QCommandLineOption metaResourceOption { + u"meta-resource"_s, + QCoreApplication::translate("main", "Qt meta information file (in .qrc format)"), + QCoreApplication::translate("main", "meta file name") + }; + parser.addOption(metaResourceOption); + QCommandLineOption namespaceOption { + u"namespace"_s, QCoreApplication::translate("main", "Namespace of the generated C++ code"), + QCoreApplication::translate("main", "namespace") + }; + parser.addOption(namespaceOption); + QCommandLineOption moduleOption{ + u"module"_s, + QCoreApplication::translate("main", + "Name of the QML module that this QML code belongs to."), + QCoreApplication::translate("main", "module") + }; + parser.addOption(moduleOption); + QCommandLineOption exportOption{ u"export"_s, + QCoreApplication::translate( + "main", "Export macro used in the generated C++ code"), + QCoreApplication::translate("main", "export") }; + parser.addOption(exportOption); + QCommandLineOption exportIncludeOption{ + u"exportInclude"_s, + QCoreApplication::translate( + "main", "Header defining the export macro to be used in the generated C++ code"), + QCoreApplication::translate("main", "exportInclude") + }; + parser.addOption(exportIncludeOption); + + parser.process(app); + + const QStringList sources = parser.positionalArguments(); + if (sources.size() != 1) { + if (sources.isEmpty()) { + parser.showHelp(); + } else { + fprintf(stderr, "%s\n", + qPrintable(u"Too many input files specified: '"_s + sources.join(u"' '"_s) + + u'\'')); + } + return EXIT_FAILURE; + } + const QString inputFile = sources.first(); + + QString url = parseUrlArgument(inputFile); + if (url.isNull()) + return EXIT_FAILURE; + if (!url.endsWith(u".qml")) { + fprintf(stderr, "Non-QML file passed as input\n"); + return EXIT_FAILURE; + } + + static QRegularExpression nameChecker(u"^[a-zA-Z_][a-zA-Z0-9_]*\\.qml$"_s); + if (auto match = nameChecker.match(QUrl(url).fileName()); !match.hasMatch()) { + fprintf(stderr, + "The given QML filename is unsuited for type compilation: the name must consist of " + "letters, digits and underscores, starting with " + "a letter or an underscore and ending in '.qml'!\n"); + return EXIT_FAILURE; + } + + QString sourceCode = loadUrl(url); + if (sourceCode.isEmpty()) + return EXIT_FAILURE; + + QString implicitImportDirectory = getImplicitImportDirectory(url); + if (implicitImportDirectory.isEmpty()) + return EXIT_FAILURE; + + QStringList importPaths; + + if (parser.isSet(resourceOption)) { + importPaths.append(QLatin1String(":/qt-project.org/imports")); + importPaths.append(QLatin1String(":/qt/qml")); + }; + + if (parser.isSet(importPathOption)) + importPaths.append(parser.values(importPathOption)); + + if (!parser.isSet(bareOption)) + importPaths.append(QLibraryInfo::path(QLibraryInfo::QmlImportsPath)); + + QStringList qmldirFiles = parser.values(qmldirOption); + + QString outputCppFile; + if (!parser.isSet(outputCppOption)) { + outputCppFile = url.first(url.size() - 3) + u"cpp"_s; + } else { + outputCppFile = parser.value(outputCppOption); + } + + QString outputHFile; + if (!parser.isSet(outputHOption)) { + outputHFile = url.first(url.size() - 3) + u"h"_s; + } else { + outputHFile = parser.value(outputHOption); + } + + if (!parser.isSet(resourceOption)) { + fprintf(stderr, "No resource paths for file: %s\n", qPrintable(inputFile)); + return EXIT_FAILURE; + } + + // main logic: + QQmlJS::Engine engine; + QQmlJS::Lexer lexer(&engine); + lexer.setCode(sourceCode, /*lineno = */ 1); + QQmlJS::Parser qmlParser(&engine); + if (!qmlParser.parse()) { + const auto diagnosticMessages = qmlParser.diagnosticMessages(); + for (const QQmlJS::DiagnosticMessage &m : diagnosticMessages) { + fprintf(stderr, "%s\n", + qPrintable(QStringLiteral("%1:%2:%3: %4") + .arg(inputFile) + .arg(m.loc.startLine) + .arg(m.loc.startColumn) + .arg(m.message))); + } + return EXIT_FAILURE; + } + + const QStringList resourceFiles = parser.values(resourceOption); + QQmlJSResourceFileMapper mapper(resourceFiles); + const QStringList metaResourceFiles = parser.values(metaResourceOption); + QQmlJSResourceFileMapper metaDataMapper(metaResourceFiles); + + const auto firstQml = [](const QStringList &paths) { + auto it = std::find_if(paths.cbegin(), paths.cend(), + [](const QString &x) { return x.endsWith(u".qml"_s); }); + if (it == paths.cend()) + return QString(); + return *it; + }; + // verify that we can map current file to qrc (then use the qrc path later) + const QStringList paths = mapper.resourcePaths(QQmlJSResourceFileMapper::localFileFilter(url)); + if (paths.isEmpty()) { + fprintf(stderr, "Failed to find a resource path for file: %s\n", qPrintable(inputFile)); + return EXIT_FAILURE; + } else if (paths.size() > 1) { + bool good = !firstQml(paths).isEmpty(); + good &= std::any_of(paths.cbegin(), paths.cend(), + [](const QString &x) { return x.endsWith(u".h"_s); }); + if (!good || paths.size() > 2) { + fprintf(stderr, "Unexpected resource paths for file: %s\n", qPrintable(inputFile)); + return EXIT_FAILURE; + } + } + + QmltcCompilerInfo info; + info.outputCppFile = parser.value(outputCppOption); + info.outputHFile = parser.value(outputHOption); + info.resourcePath = firstQml(paths); + info.outputNamespace = parser.value(namespaceOption); + info.exportMacro = parser.value(exportOption); + info.exportInclude = parser.value(exportIncludeOption); + + if (info.outputCppFile.isEmpty()) { + fprintf(stderr, "An output C++ file is required. Pass one using --impl"); + return EXIT_FAILURE; + } + if (info.outputHFile.isEmpty()) { + fprintf(stderr, "An output C++ header file is required. Pass one using --header"); + return EXIT_FAILURE; + } + + QQmlJSImporter importer { importPaths, &mapper }; + importer.setMetaDataMapper(&metaDataMapper); + auto qmltcVisitor = [](QQmlJS::AST::Node *rootNode, QQmlJSImporter *self, + const QQmlJSImporter::ImportVisitorPrerequisites &p) { + QmltcVisitor v(p.m_target, self, p.m_logger, p.m_implicitImportDirectory, p.m_qmldirFiles); + QQmlJS::AST::Node::accept(rootNode, &v); + }; + importer.setImportVisitor(qmltcVisitor); + + QQmlJSLogger logger; + logger.setFileName(url); + logger.setCode(sourceCode); + setupLogger(logger); + + auto currentScope = QQmlJSScope::create(); + if (parser.isSet(moduleOption)) + currentScope->setOwnModuleName(parser.value(moduleOption)); + + QmltcVisitor visitor(currentScope, &importer, &logger, + QQmlJSImportVisitor::implicitImportDirectory(url, &mapper), qmldirFiles); + visitor.setMode(QmltcVisitor::Compile); + QmltcTypeResolver typeResolver { &importer }; + typeResolver.init(&visitor, qmlParser.rootNode()); + + using PassManagerPtr = + std::unique_ptr<QQmlSA::PassManager, + decltype(&QQmlSA::PassManagerPrivate::deletePassManager)>; + PassManagerPtr passMan(QQmlSA::PassManagerPrivate::createPassManager(&visitor, &typeResolver), + &QQmlSA::PassManagerPrivate::deletePassManager); + passMan->registerPropertyPass(std::make_unique<QQmlJSLiteralBindingCheck>(passMan.get()), + QString(), QString(), QString()); + passMan->analyze(QQmlJSScope::createQQmlSAElement(visitor.result())); + + if (logger.hasErrors()) + return EXIT_FAILURE; + + QList<QQmlJS::DiagnosticMessage> warnings = importer.takeGlobalWarnings(); + if (!warnings.isEmpty()) { + logger.log(QStringLiteral("Type warnings occurred while compiling file:"), qmlImport, + QQmlJS::SourceLocation()); + logger.processMessages(warnings, qmlImport); + // Log_Import is critical for the compiler + return EXIT_FAILURE; + } + + QmltcCompiler compiler(url, &typeResolver, &visitor, &logger); + compiler.compile(info); + + if (logger.hasErrors()) + return EXIT_FAILURE; + + return EXIT_SUCCESS; +} diff --git a/tools/qmltc/qmltccodewriter.cpp b/tools/qmltc/qmltccodewriter.cpp new file mode 100644 index 0000000000..134de0f98e --- /dev/null +++ b/tools/qmltc/qmltccodewriter.cpp @@ -0,0 +1,569 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qmltccodewriter.h" + +#include <QtCore/qfileinfo.h> +#include <QtCore/qstringbuilder.h> +#include <QtCore/qstring.h> +#include <QtCore/qmap.h> +#include <QtCore/qlist.h> + +#include <utility> + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +static QString urlToMacro(const QString &url) +{ + QFileInfo fi(url); + return u"Q_QMLTC_" + fi.baseName().toUpper(); +} + +static QString getFunctionCategory(const QmltcMethodBase &method) +{ + QString category; + switch (method.access) { + case QQmlJSMetaMethod::Private: + category = u"private"_s; + break; + case QQmlJSMetaMethod::Protected: + category = u"protected"_s; + break; + case QQmlJSMetaMethod::Public: + category = u"public"_s; + break; + } + return category; +} + +static QString getFunctionCategory(const QmltcMethod &method) +{ + QString category = getFunctionCategory(static_cast<const QmltcMethodBase &>(method)); + switch (method.type) { + case QQmlJSMetaMethodType::Signal: + category = u"Q_SIGNALS"_s; + break; + case QQmlJSMetaMethodType::Slot: + category += u" Q_SLOTS"_s; + break; + case QQmlJSMetaMethodType::Method: + case QQmlJSMetaMethodType::StaticMethod: + break; + } + return category; +} + +static QString appendSpace(const QString &s) +{ + if (s.isEmpty()) + return s; + return s + u" "; +} + +static QString prependSpace(const QString &s) +{ + if (s.isEmpty()) + return s; + return u" " + s; +} + +static std::pair<QString, QString> functionSignatures(const QmltcMethodBase &method) +{ + const QString name = method.name; + const QList<QmltcVariable> ¶meterList = method.parameterList; + + QStringList headerParamList; + QStringList cppParamList; + for (const QmltcVariable &variable : parameterList) { + const QString commonPart = variable.cppType + u" " + variable.name; + cppParamList << commonPart; + headerParamList << commonPart; + if (!variable.defaultValue.isEmpty()) + headerParamList.back() += u" = " + variable.defaultValue; + } + + const QString headerSignature = name + u"(" + headerParamList.join(u", "_s) + u")" + + prependSpace(method.modifiers.join(u" ")); + const QString cppSignature = name + u"(" + cppParamList.join(u", "_s) + u")" + + prependSpace(method.modifiers.join(u" ")); + return { headerSignature, cppSignature }; +} + +static QString functionReturnType(const QmltcMethod &m) +{ + return appendSpace(m.declarationPrefixes.join(u" "_s)) + m.returnType; +} + +void QmltcCodeWriter::writeGlobalHeader(QmltcOutputWrapper &code, const QString &sourcePath, + const QString &hPath, const QString &cppPath, + const QString &outNamespace, + const QSet<QString> &requiredCppIncludes) +{ + Q_UNUSED(cppPath); + const QString preamble = u"// This code is auto-generated by the qmltc tool from the file '" + + sourcePath + u"'\n// WARNING! All changes made in this file will be lost!\n"; + code.rawAppendToHeader(preamble); + code.rawAppendToCpp(preamble); + code.rawAppendToHeader( + u"// NOTE: This generated API is to be considered implementation detail."); + code.rawAppendToHeader( + u"// It may change from version to version and should not be relied upon."); + + const QString headerMacro = urlToMacro(sourcePath); + code.rawAppendToHeader(u"#ifndef %1_H"_s.arg(headerMacro)); + code.rawAppendToHeader(u"#define %1_H"_s.arg(headerMacro)); + + code.rawAppendToHeader(u"#include <QtCore/qproperty.h>"); + code.rawAppendToHeader(u"#include <QtCore/qobject.h>"); + code.rawAppendToHeader(u"#include <QtCore/qcoreapplication.h>"); + code.rawAppendToHeader(u"#include <QtCore/qxpfunctional.h>"); + code.rawAppendToHeader(u"#include <QtQml/qqmlengine.h>"); + code.rawAppendToHeader(u"#include <QtCore/qurl.h>"); // used in engine execution + code.rawAppendToHeader(u"#include <QtQml/qqml.h>"); // used for attached properties + + code.rawAppendToHeader(u"#include <private/qqmlengine_p.h>"); // executeRuntimeFunction(), etc. + code.rawAppendToHeader(u"#include <private/qqmltcobjectcreationhelper_p.h>"); // QmltcSupportLib + + code.rawAppendToHeader(u"#include <QtQml/qqmllist.h>"); // QQmlListProperty + + // include custom C++ includes required by used types + code.rawAppendToHeader(u"// BEGIN(custom_cpp_includes)"); + for (const auto &requiredInclude : requiredCppIncludes) + code.rawAppendToHeader(u"#include \"" + requiredInclude + u"\""); + code.rawAppendToHeader(u"// END(custom_cpp_includes)"); + + code.rawAppendToCpp(u"#include \"" + hPath + u"\""); // include own .h file + code.rawAppendToCpp(u"// qmltc support library:"); + code.rawAppendToCpp(u"#include <private/qqmlcppbinding_p.h>"); // QmltcSupportLib + code.rawAppendToCpp(u"#include <private/qqmlcpponassignment_p.h>"); // QmltcSupportLib + code.rawAppendToHeader(u"#include <private/qqmlcpptypehelpers_p.h> "); // QmltcSupportLib + + code.rawAppendToCpp(u"#include <private/qqmlobjectcreator_p.h>"); // createComponent() + code.rawAppendToCpp(u"#include <private/qqmlcomponent_p.h>"); // QQmlComponentPrivate::get() + + code.rawAppendToCpp(u""); + code.rawAppendToCpp(u"#include <private/qobject_p.h>"); // NB: for private properties + code.rawAppendToCpp(u"#include <private/qqmlobjectcreator_p.h>"); // for finalize callbacks + code.rawAppendToCpp(u"#include <QtQml/qqmlprivate.h>"); // QQmlPrivate::qmlExtendedObject() + + code.rawAppendToCpp(u""); // blank line + code.rawAppendToCpp(u"QT_USE_NAMESPACE // avoid issues with QT_NAMESPACE"); + + code.rawAppendToHeader(u""); // blank line + + const QStringList namespaces = outNamespace.split(u"::"_s); + + for (const QString ¤tNamespace : namespaces) { + code.rawAppendToHeader(u"namespace %1 {"_s.arg(currentNamespace)); + code.rawAppendToCpp(u"namespace %1 {"_s.arg(currentNamespace)); + } +} + +void QmltcCodeWriter::write(QmltcOutputWrapper &code, + const QmltcPropertyInitializer &propertyInitializer, + const QmltcType &wrappedType) +{ + code.rawAppendToHeader(u"class " + propertyInitializer.name + u" {"); + + { + { + [[maybe_unused]] QmltcOutputWrapper::HeaderIndentationScope headerIndent(&code); + + code.rawAppendToHeader(u"friend class " + wrappedType.cppType + u";"); + } + + code.rawAppendToHeader(u"public:"_s); + + [[maybe_unused]] QmltcOutputWrapper::MemberNameScope typeScope(&code, propertyInitializer.name); + { + [[maybe_unused]] QmltcOutputWrapper::HeaderIndentationScope headerIndent(&code); + + write(code, propertyInitializer.constructor); + code.rawAppendToHeader(u""); // blank line + + for (const auto &propertySetter : propertyInitializer.propertySetters) { + write(code, propertySetter); + } + } + + code.rawAppendToHeader(u""); // blank line + code.rawAppendToHeader(u"private:"_s); + + { + [[maybe_unused]] QmltcOutputWrapper::HeaderIndentationScope headerIndent(&code); + + write(code, propertyInitializer.component); + write(code, propertyInitializer.initializedCache); + } + } + + code.rawAppendToHeader(u"};"_s); + code.rawAppendToHeader(u""); // blank line +} + +void QmltcCodeWriter::write(QmltcOutputWrapper &code, const QmltcRequiredPropertiesBundle &requiredPropertiesBundle) +{ + code.rawAppendToHeader(u"struct " + requiredPropertiesBundle.name + u" {"); + + { + [[maybe_unused]] QmltcOutputWrapper::HeaderIndentationScope headerIndent(&code); + + for (const auto &member : requiredPropertiesBundle.members) { + write(code, member); + } + } + + code.rawAppendToHeader(u"};"_s); + code.rawAppendToHeader(u""); // blank line +} + +void QmltcCodeWriter::writeGlobalFooter(QmltcOutputWrapper &code, const QString &sourcePath, + const QString &outNamespace) +{ + const QStringList namespaces = outNamespace.split(u"::"_s); + + for (auto it = namespaces.crbegin(), end = namespaces.crend(); it != end; it++) { + code.rawAppendToCpp(u"} // namespace %1"_s.arg(*it)); + code.rawAppendToHeader(u"} // namespace %1"_s.arg(*it)); + } + + code.rawAppendToHeader(u""); // blank line + code.rawAppendToHeader(u"#endif // %1_H"_s.arg(urlToMacro(sourcePath))); + code.rawAppendToHeader(u""); // blank line +} + +static void writeToFile(const QString &path, const QByteArray &data) +{ + // When not using dependency files, changing a single qml invalidates all + // qml files and would force the recompilation of everything. To avoid that, + // we check if the data is equal to the existing file, if yes, don't touch + // it so the build system will not recompile unnecessary things. + // + // If the build system use dependency file, we should anyway touch the file + // so qmltc is not re-run + QFileInfo fi(path); + if (fi.exists() && fi.size() == data.size()) { + QFile oldFile(path); + if (oldFile.open(QIODevice::ReadOnly)) { + if (oldFile.readAll() == data) + return; + } + } + QFile file(path); + if (!file.open(QIODevice::WriteOnly)) + qFatal("Could not open file %s", qPrintable(path)); + file.write(data); +} + +void QmltcCodeWriter::write(QmltcOutputWrapper &code, const QmltcProgram &program) +{ + writeGlobalHeader(code, program.url, program.hPath, program.cppPath, program.outNamespace, + program.includes); + + // url method comes first + writeUrl(code, program.urlMethod); + + // forward declare all the types first + for (const QmltcType &type : std::as_const(program.compiledTypes)) + code.rawAppendToHeader(u"class " + type.cppType + u";"); + // write all the types and their content + for (const QmltcType &type : std::as_const(program.compiledTypes)) + write(code, type, program.exportMacro); + + // add typeCount definitions. after all types have been written down (so + // they are now complete types as per C++). practically, this only concerns + // document root type + for (const QmltcType &type : std::as_const(program.compiledTypes)) { + if (!type.typeCount) + continue; + code.rawAppendToHeader(u""); // blank line + code.rawAppendToHeader(u"constexpr %1 %2::%3()"_s.arg(type.typeCount->returnType, + type.cppType, type.typeCount->name)); + code.rawAppendToHeader(u"{"); + for (const QString &line : std::as_const(type.typeCount->body)) + code.rawAppendToHeader(line, 1); + code.rawAppendToHeader(u"}"); + } + + writeGlobalFooter(code, program.url, program.outNamespace); + + writeToFile(program.hPath, code.code().header.toUtf8()); + writeToFile(program.cppPath, code.code().cpp.toUtf8()); +} + +template<typename Predicate> +static void dumpFunctions(QmltcOutputWrapper &code, const QList<QmltcMethod> &functions, + Predicate pred) +{ + // functions are _ordered_ by access and kind. ordering is important to + // provide consistent output + QMap<QString, QList<const QmltcMethod *>> orderedFunctions; + for (const auto &function : functions) { + if (pred(function)) + orderedFunctions[getFunctionCategory(function)].append(std::addressof(function)); + } + + for (auto it = orderedFunctions.cbegin(); it != orderedFunctions.cend(); ++it) { + code.rawAppendToHeader(it.key() + u":", -1); + for (const QmltcMethod *function : std::as_const(it.value())) + QmltcCodeWriter::write(code, *function); + } +} + +void QmltcCodeWriter::write(QmltcOutputWrapper &code, const QmltcType &type, + const QString &exportMacro) +{ + const auto constructClassString = [&]() { + QString str = u"class "_s; + if (!exportMacro.isEmpty()) + str.append(exportMacro).append(u" "_s); + str.append(type.cppType); + QStringList nonEmptyBaseClasses; + nonEmptyBaseClasses.reserve(type.baseClasses.size()); + std::copy_if(type.baseClasses.cbegin(), type.baseClasses.cend(), + std::back_inserter(nonEmptyBaseClasses), + [](const QString &entry) { return !entry.isEmpty(); }); + if (!nonEmptyBaseClasses.isEmpty()) + str += u" : public " + nonEmptyBaseClasses.join(u", public "_s); + return str; + }; + + code.rawAppendToHeader(u""); // blank line + code.rawAppendToCpp(u""); // blank line + + code.rawAppendToHeader(constructClassString()); + code.rawAppendToHeader(u"{"); + for (const QString &mocLine : std::as_const(type.mocCode)) + code.rawAppendToHeader(mocLine, 1); + + QmltcOutputWrapper::MemberNameScope typeScope(&code, type.cppType); + Q_UNUSED(typeScope); + { + QmltcOutputWrapper::HeaderIndentationScope headerIndent(&code); + Q_UNUSED(headerIndent); + + // first, write user-visible code, then everything else. someone might + // want to look at the generated code, so let's make an effort when + // writing it down + + code.rawAppendToHeader(u"/* ----------------- */"); + code.rawAppendToHeader(u"/* External C++ API */"); + code.rawAppendToHeader(u"public:", -1); + + if (!type.propertyInitializer.name.isEmpty()) + write(code, type.propertyInitializer, type); + + if (type.requiredPropertiesBundle) + write(code, *type.requiredPropertiesBundle); + + // NB: when non-document root, the externalCtor won't be public - but we + // really don't care about the output format of such types + if (!type.ignoreInit && type.externalCtor.access == QQmlJSMetaMethod::Public) { + // TODO: ignoreInit must be eliminated + + QmltcCodeWriter::write(code, type.externalCtor); + if (type.staticCreate) + QmltcCodeWriter::write(code, *type.staticCreate); + } + + // dtor + if (type.dtor) + QmltcCodeWriter::write(code, *type.dtor); + + // enums + for (const auto &enumeration : std::as_const(type.enums)) + QmltcCodeWriter::write(code, enumeration); + + // visible functions + const auto isUserVisibleFunction = [](const QmltcMethod &function) { + return function.userVisible; + }; + dumpFunctions(code, type.functions, isUserVisibleFunction); + + code.rawAppendToHeader(u"/* ----------------- */"); + code.rawAppendToHeader(u""); // blank line + code.rawAppendToHeader(u"/* Internal functionality (do NOT use it!) */"); + + // below are the hidden parts of the type + + // (rest of the) ctors + if (type.ignoreInit) { // TODO: this branch should be eliminated + Q_ASSERT(type.baselineCtor.access == QQmlJSMetaMethod::Public); + code.rawAppendToHeader(u"public:", -1); + QmltcCodeWriter::write(code, type.baselineCtor); + } else { + code.rawAppendToHeader(u"protected:", -1); + if (type.externalCtor.access != QQmlJSMetaMethod::Public) { + Q_ASSERT(type.externalCtor.access == QQmlJSMetaMethod::Protected); + QmltcCodeWriter::write(code, type.externalCtor); + } + QmltcCodeWriter::write(code, type.baselineCtor); + QmltcCodeWriter::write(code, type.init); + QmltcCodeWriter::write(code, type.endInit); + QmltcCodeWriter::write(code, type.setComplexBindings); + QmltcCodeWriter::write(code, type.beginClass); + QmltcCodeWriter::write(code, type.completeComponent); + QmltcCodeWriter::write(code, type.finalizeComponent); + QmltcCodeWriter::write(code, type.handleOnCompleted); + } + + // children + for (const auto &child : std::as_const(type.children)) + QmltcCodeWriter::write(code, child, exportMacro); + + // (non-visible) functions + dumpFunctions(code, type.functions, std::not_fn(isUserVisibleFunction)); + + // variables and properties + if (!type.variables.isEmpty() || !type.properties.isEmpty()) { + code.rawAppendToHeader(u""); // blank line + code.rawAppendToHeader(u"protected:", -1); + } + for (const auto &property : std::as_const(type.properties)) + write(code, property); + for (const auto &variable : std::as_const(type.variables)) + write(code, variable); + } + + code.rawAppendToHeader(u"private:", -1); + for (const QString &otherLine : std::as_const(type.otherCode)) + code.rawAppendToHeader(otherLine, 1); + + if (type.typeCount) { + // add typeCount declaration, definition is added later + code.rawAppendToHeader(u""); // blank line + code.rawAppendToHeader(u"protected:"); + code.rawAppendToHeader(u"constexpr static %1 %2();"_s.arg(type.typeCount->returnType, + type.typeCount->name), + 1); + } + + code.rawAppendToHeader(u"};"); +} + +void QmltcCodeWriter::write(QmltcOutputWrapper &code, const QmltcEnum &enumeration) +{ + code.rawAppendToHeader(u"enum " + enumeration.cppType + u" {"); + for (qsizetype i = 0; i < enumeration.keys.size(); ++i) { + QString str; + if (enumeration.values.isEmpty()) { + str += enumeration.keys.at(i) + u","; + } else { + str += enumeration.keys.at(i) + u" = " + enumeration.values.at(i) + u","; + } + code.rawAppendToHeader(str, 1); + } + code.rawAppendToHeader(u"};"); + code.rawAppendToHeader(enumeration.ownMocLine); +} + +void QmltcCodeWriter::write(QmltcOutputWrapper &code, const QmltcMethod &method) +{ + const auto [hSignature, cppSignature] = functionSignatures(method); + // Note: augment return type with preambles in declaration + code.rawAppendToHeader((method.type == QQmlJSMetaMethodType::StaticMethod + ? u"static " + functionReturnType(method) + : functionReturnType(method)) + + u" " + hSignature + u";"); + + // do not generate method implementation if it is a signal + const auto methodType = method.type; + if (methodType != QQmlJSMetaMethodType::Signal) { + code.rawAppendToCpp(u""_s); // blank line + if (method.comments.size() > 0) { + code.rawAppendToCpp(u"/*! \\internal"_s); + for (const auto &comment : method.comments) + code.rawAppendToCpp(comment, 1); + code.rawAppendToCpp(u"*/"_s); + } + code.rawAppendToCpp(method.returnType); + code.rawAppendSignatureToCpp(cppSignature); + code.rawAppendToCpp(u"{"); + { + QmltcOutputWrapper::CppIndentationScope cppIndent(&code); + Q_UNUSED(cppIndent); + for (const QString &line : std::as_const(method.body)) + code.rawAppendToCpp(line); + } + code.rawAppendToCpp(u"}"); + } +} + +template<typename WriteInitialization> +static void writeSpecialMethod(QmltcOutputWrapper &code, const QmltcMethodBase &specialMethod, + WriteInitialization writeInit) +{ + const auto [hSignature, cppSignature] = functionSignatures(specialMethod); + code.rawAppendToHeader(hSignature + u";"); + + code.rawAppendToCpp(u""); // blank line + code.rawAppendSignatureToCpp(cppSignature); + + writeInit(specialMethod); + + code.rawAppendToCpp(u"{"); + { + QmltcOutputWrapper::CppIndentationScope cppIndent(&code); + Q_UNUSED(cppIndent); + for (const QString &line : std::as_const(specialMethod.body)) + code.rawAppendToCpp(line); + } + code.rawAppendToCpp(u"}"); +} + +void QmltcCodeWriter::write(QmltcOutputWrapper &code, const QmltcCtor &ctor) +{ + const auto writeInitializerList = [&](const QmltcMethodBase &ctorBase) { + auto ctor = static_cast<const QmltcCtor &>(ctorBase); + if (!ctor.initializerList.isEmpty()) { + code.rawAppendToCpp(u":", 1); + // double \n to make separate initializer list lines stand out more + code.rawAppendToCpp( + ctor.initializerList.join(u",\n\n" + u" "_s.repeated(code.cppIndent + 1)), + 1); + } + }; + + writeSpecialMethod(code, ctor, writeInitializerList); +} + +void QmltcCodeWriter::write(QmltcOutputWrapper &code, const QmltcDtor &dtor) +{ + const auto noop = [](const QmltcMethodBase &) {}; + writeSpecialMethod(code, dtor, noop); +} + +void QmltcCodeWriter::write(QmltcOutputWrapper &code, const QmltcVariable &var) +{ + const QString optionalPart = var.defaultValue.isEmpty() ? u""_s : u" = " + var.defaultValue; + code.rawAppendToHeader(var.cppType + u" " + var.name + optionalPart + u";"); +} + +void QmltcCodeWriter::write(QmltcOutputWrapper &code, const QmltcProperty &prop) +{ + Q_ASSERT(prop.defaultValue.isEmpty()); // we don't support it yet (or at all?) + code.rawAppendToHeader(u"Q_OBJECT_BINDABLE_PROPERTY(%1, %2, %3, &%1::%4)"_s.arg( + prop.containingClass, prop.cppType, prop.name, prop.signalName)); +} + +void QmltcCodeWriter::writeUrl(QmltcOutputWrapper &code, const QmltcMethod &urlMethod) +{ + // unlike ordinary methods, url function only exists in .cpp + Q_ASSERT(!urlMethod.returnType.isEmpty()); + const auto [hSignature, _] = functionSignatures(urlMethod); + Q_UNUSED(_); + // Note: augment return type with preambles in declaration + code.rawAppendToCpp(functionReturnType(urlMethod) + u" " + hSignature); + code.rawAppendToCpp(u"{"); + { + QmltcOutputWrapper::CppIndentationScope cppIndent(&code); + Q_UNUSED(cppIndent); + for (const QString &line : std::as_const(urlMethod.body)) + code.rawAppendToCpp(line); + } + code.rawAppendToCpp(u"}"); +} + +QT_END_NAMESPACE diff --git a/tools/qmltc/qmltccodewriter.h b/tools/qmltc/qmltccodewriter.h new file mode 100644 index 0000000000..20b0262737 --- /dev/null +++ b/tools/qmltc/qmltccodewriter.h @@ -0,0 +1,39 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef QMLTCCODEWRITER_H +#define QMLTCCODEWRITER_H + +#include "qmltcoutputprimitives.h" +#include "qmltcoutputir.h" + +#include <QtCore/qstring.h> + +QT_BEGIN_NAMESPACE + +struct QmltcCodeWriter +{ + static void writeGlobalHeader(QmltcOutputWrapper &code, const QString &sourcePath, + const QString &hPath, const QString &cppPath, + const QString &outNamespace, + const QSet<QString> &requiredCppIncludes); + static void writeGlobalFooter(QmltcOutputWrapper &code, const QString &sourcePath, + const QString &outNamespace); + static void write(QmltcOutputWrapper &code, const QmltcProgram &program); + static void write(QmltcOutputWrapper &code, const QmltcType &type, const QString &exportMacro); + static void write(QmltcOutputWrapper &code, const QmltcEnum &enumeration); + static void write(QmltcOutputWrapper &code, const QmltcMethod &method); + static void write(QmltcOutputWrapper &code, const QmltcCtor &ctor); + static void write(QmltcOutputWrapper &code, const QmltcDtor &dtor); + static void write(QmltcOutputWrapper &code, const QmltcVariable &var); + static void write(QmltcOutputWrapper &code, const QmltcProperty &prop); + static void write(QmltcOutputWrapper &code, const QmltcPropertyInitializer &propertyInitializer, const QmltcType& wrappedType); + static void write(QmltcOutputWrapper &code, const QmltcRequiredPropertiesBundle &requiredPropertiesBundle); + +private: + static void writeUrl(QmltcOutputWrapper &code, const QmltcMethod &urlMethod); // special +}; + +QT_END_NAMESPACE + +#endif // QMLTCCODEWRITER_H diff --git a/tools/qmltc/qmltccommandlineutils.cpp b/tools/qmltc/qmltccommandlineutils.cpp new file mode 100644 index 0000000000..e3f6b4d3b7 --- /dev/null +++ b/tools/qmltc/qmltccommandlineutils.cpp @@ -0,0 +1,69 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qmltccommandlineutils.h" + +#include <QtCore/qstring.h> +#include <QtCore/qfileinfo.h> +#include <QtCore/qdir.h> +#include <QtCore/qurl.h> +#include <QtCore/qlibraryinfo.h> + +QT_BEGIN_NAMESPACE + +QString parseUrlArgument(const QString &arg) +{ + const QUrl url = QUrl::fromUserInput(arg, QDir::currentPath(), QUrl::AssumeLocalFile); + if (!url.isValid()) { + fprintf(stderr, "Invalid URL: \"%s\"\n", qPrintable(arg)); + return QString(); + } + if (!url.isLocalFile()) { + fprintf(stderr, "\"%s\" is not a local file\n", qPrintable(arg)); + return QString(); + } + return url.toLocalFile(); +} + +QString loadUrl(const QString &url) +{ + const QFileInfo fi(url); + if (!fi.exists()) { + fprintf(stderr, "\"%s\" does not exist.\n", + qPrintable(QDir::toNativeSeparators(fi.absoluteFilePath()))); + return QString(); + } + + QFile f(fi.absoluteFilePath()); + if (!f.open(QIODevice::ReadOnly)) { + fprintf(stderr, "Unable to read \"%s\": %s.\n", + qPrintable(QDir::toNativeSeparators(fi.absoluteFilePath())), + qPrintable(f.errorString())); + return QString(); + } + + QByteArray data(fi.size(), Qt::Uninitialized); + if (f.read(data.data(), data.size()) != data.size()) { + fprintf(stderr, "Unable to read \"%s\": %s.\n", + qPrintable(QDir::toNativeSeparators(fi.absoluteFilePath())), + qPrintable(f.errorString())); + return QString(); + } + return QString::fromUtf8(data); +} + +QString getImplicitImportDirectory(const QString &url) +{ + const QFileInfo fi(url); + Q_ASSERT(fi.exists()); + QDir dir = fi.dir(); + QString implicitImport = dir.canonicalPath(); // resolves symlinks, etc. + if (implicitImport.isEmpty()) { + fprintf(stderr, "Cannot resolve implicit import directory of \"%s\"", + qPrintable(QDir::toNativeSeparators(fi.absoluteFilePath()))); + return QString(); + } + return implicitImport; +} + +QT_END_NAMESPACE diff --git a/tools/qmltc/qmltccommandlineutils.h b/tools/qmltc/qmltccommandlineutils.h new file mode 100644 index 0000000000..8ad3232d1f --- /dev/null +++ b/tools/qmltc/qmltccommandlineutils.h @@ -0,0 +1,17 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef QMLTCCOMMANDLINEUTILS_H +#define QMLTCCOMMANDLINEUTILS_H + +#include <QtCore/qstring.h> + +QT_BEGIN_NAMESPACE + +QString parseUrlArgument(const QString &arg); +QString loadUrl(const QString &url); +QString getImplicitImportDirectory(const QString &url); + +QT_END_NAMESPACE + +#endif // QMLTCCOMMANDLINEUTILS_H diff --git a/tools/qmltc/qmltccompiler.cpp b/tools/qmltc/qmltccompiler.cpp new file mode 100644 index 0000000000..75bd580e07 --- /dev/null +++ b/tools/qmltc/qmltccompiler.cpp @@ -0,0 +1,2053 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qmltccompiler.h" +#include "qmltcoutputir.h" +#include "qmltccodewriter.h" +#include "qmltcpropertyutils.h" +#include "qmltccompilerpieces.h" + +#include <QtCore/qloggingcategory.h> +#include <QtQml/private/qqmlsignalnames_p.h> +#include <private/qqmljsutils_p.h> + +#include <algorithm> + +QT_BEGIN_NAMESPACE +using namespace Qt::StringLiterals; + +bool qIsReferenceTypeList(const QQmlJSMetaProperty &p) +{ + if (QQmlJSScope::ConstPtr type = p.type()) + return type->isListProperty(); + return false; +} + +static QList<QQmlJSMetaProperty> unboundRequiredProperties( + const QQmlJSScope::ConstPtr &type, + QmltcTypeResolver *resolver +) { + QList<QQmlJSMetaProperty> requiredProperties{}; + + auto isPropertyRequired = [&type, &resolver](const auto &property) { + if (!type->isPropertyRequired(property.propertyName())) + return false; + + if (type->hasPropertyBindings(property.propertyName())) + return false; + + if (property.isAlias()) { + QQmlJSUtils::AliasResolutionVisitor aliasVisitor; + + QQmlJSUtils::ResolvedAlias result = + QQmlJSUtils::resolveAlias(resolver, property, type, aliasVisitor); + + if (result.kind != QQmlJSUtils::AliasTarget_Property) + return false; + + // If the top level alias targets a property that is in + // the top level scope and that property is required, then + // we will already pick up the property during one of the + // iterations. + // Setting the property or the alias is the same so we + // discard one of the two, as otherwise we would require + // the user to pass two values for the same property ,in + // this case the alias. + // + // For example in: + // + // ``` + // Item { + // id: self + // required property int foo + // property alias bar: self.foo + // } + // ``` + // + // Both foo and bar are required but setting one or the + // other is the same operation so that we should choose + // only one. + if (result.owner == type && + type->isPropertyRequired(result.property.propertyName())) + return false; + + if (result.owner->hasPropertyBindings(result.property.propertyName())) + return false; + } + + return true; + }; + + const auto properties = type->properties(); + std::copy_if(properties.cbegin(), properties.cend(), + std::back_inserter(requiredProperties), isPropertyRequired); + std::sort(requiredProperties.begin(), requiredProperties.end(), + [](const auto &left, const auto &right) { + return left.propertyName() < right.propertyName(); + }); + + return requiredProperties; +} + + +// Populates the internal representation for a +// RequiredPropertiesBundle, a class that acts as a bundle of initial +// values that should be set for the required properties of a type. +static void compileRequiredPropertiesBundle( + QmltcType ¤t, + const QQmlJSScope::ConstPtr &type, + QmltcTypeResolver *resolver +) { + + QList<QQmlJSMetaProperty> requiredProperties = unboundRequiredProperties(type, resolver); + + if (requiredProperties.isEmpty()) + return; + + current.requiredPropertiesBundle.emplace(); + current.requiredPropertiesBundle->name = u"RequiredPropertiesBundle"_s; + + current.requiredPropertiesBundle->members.reserve(requiredProperties.size()); + std::transform(requiredProperties.cbegin(), requiredProperties.cend(), + std::back_inserter(current.requiredPropertiesBundle->members), + [](const QQmlJSMetaProperty &property) { + QString type = qIsReferenceTypeList(property) + ? u"const QList<%1*>&"_s.arg( + property.type()->valueType()->internalName()) + : u"passByConstRefOrValue<%1>"_s.arg( + property.type()->augmentedInternalName()); + return QmltcVariable{ type, property.propertyName() }; + }); +} + +static void compileRootExternalConstructorBody( + QmltcType& current, + const QQmlJSScope::ConstPtr &type +) { + current.externalCtor.body << u"// document root:"_s; + // if it's document root, we want to create our QQmltcObjectCreationBase + // that would store all the created objects + current.externalCtor.body << u"QQmltcObjectCreationBase<%1> objectHolder;"_s.arg( + type->internalName()); + current.externalCtor.body + << u"QQmltcObjectCreationHelper creator = objectHolder.view();"_s; + current.externalCtor.body << u"creator.set(0, this);"_s; // special case + + QString initializerName = u"initializer"_s; + if (current.requiredPropertiesBundle) { + // Compose new initializer based on the initial values for required properties. + current.externalCtor.body << u"auto newInitializer = [&](auto& propertyInitializer) {"_s; + for (const auto& member : current.requiredPropertiesBundle->members) { + current.externalCtor.body << u" propertyInitializer.%1(requiredPropertiesBundle.%2);"_s.arg( + QmltcPropertyData(member.name).write, member.name + ); + } + current.externalCtor.body << u" initializer(propertyInitializer);"_s; + current.externalCtor.body << u"};"_s; + + initializerName = u"newInitializer"_s; + } + + // now call init + current.externalCtor.body << current.init.name + + u"(&creator, engine, QQmlContextData::get(engine->rootContext()), /* " + u"endInit */ true, %1);"_s.arg(initializerName); +}; + +Q_LOGGING_CATEGORY(lcQmltcCompiler, "qml.qmltc.compiler", QtWarningMsg); + +const QString QmltcCodeGenerator::privateEngineName = u"ePriv"_s; +const QString QmltcCodeGenerator::typeCountName = u"q_qmltc_typeCount"_s; + +QmltcCompiler::QmltcCompiler(const QString &url, QmltcTypeResolver *resolver, QmltcVisitor *visitor, + QQmlJSLogger *logger) + : m_url(url), m_typeResolver(resolver), m_visitor(visitor), m_logger(logger) +{ + Q_UNUSED(m_typeResolver); + Q_ASSERT(!hasErrors()); +} + +// needed due to std::unique_ptr<CodeGenerator> with CodeGenerator being +// incomplete type in the header (~std::unique_ptr<> fails with a static_assert) +QmltcCompiler::~QmltcCompiler() = default; + +QString QmltcCompiler::newSymbol(const QString &base) +{ + QString symbol = base; + symbol.replace(QLatin1String("."), QLatin1String("_")); + while (symbol.startsWith(QLatin1Char('_')) && symbol.size() >= 2 + && (symbol[1].isUpper() || symbol[1] == QLatin1Char('_'))) { + symbol.remove(0, 1); + } + if (!m_symbols.contains(symbol)) { + m_symbols.insert(symbol, 1); + } else { + symbol += u"_" + QString::number(m_symbols[symbol]++); + } + return symbol; +} + +void QmltcCompiler::compile(const QmltcCompilerInfo &info) +{ + m_info = info; + Q_ASSERT(!m_info.outputCppFile.isEmpty()); + Q_ASSERT(!m_info.outputHFile.isEmpty()); + Q_ASSERT(!m_info.resourcePath.isEmpty()); + + // Note: we only compile "pure" QML types. any component-wrapped type is + // expected to appear through a binding + + const auto isComponent = [](const QQmlJSScope::ConstPtr &type) { + auto base = type->baseType(); + return base && base->internalName() == u"QQmlComponent"_s; + }; + + QmltcCodeGenerator generator { m_url, m_visitor }; + + QmltcMethod urlMethod; + compileUrlMethod(urlMethod, generator.urlMethodName()); + m_urlMethodName = urlMethod.name; + + // sort inline components to compile them in the right order + // a inherits b => b needs to be defined in the cpp file before a! + // r is the root => r needs to be compiled at the end! + // otherwise => sort them by inline component names to have consistent output + auto sortedInlineComponentNames = m_visitor->inlineComponentNames(); + std::sort(sortedInlineComponentNames.begin(), sortedInlineComponentNames.end(), + [&](const InlineComponentOrDocumentRootName &a, + const InlineComponentOrDocumentRootName &b) { + const auto *inlineComponentAName = std::get_if<InlineComponentNameType>(&a); + const auto *inlineComponentBName = std::get_if<InlineComponentNameType>(&b); + + if (inlineComponentAName == inlineComponentBName) + return false; + + // the root comes at last, so (a < b) == true when b is the root and a is not + if (inlineComponentAName && !inlineComponentBName) + return true; + + // b requires a to be declared before b when b inherits from a, therefore (a < b) + // == true + if (inlineComponentAName && inlineComponentBName) { + QQmlJSScope::ConstPtr inlineComponentA = m_visitor->inlineComponent(a); + QQmlJSScope::ConstPtr inlineComponentB = m_visitor->inlineComponent(b); + if (inlineComponentB->inherits(inlineComponentA)) { + return true; + } else if (inlineComponentA->inherits(inlineComponentB)) { + return false; + } else { + // fallback to default sorting based on names + return *inlineComponentAName < *inlineComponentBName; + } + } + Q_ASSERT(!inlineComponentAName || !inlineComponentBName); + // a is the root or both a and b are the root + return false; + }); + + QList<QmltcType> compiledTypes; + for (const auto &inlineComponent : sortedInlineComponentNames) { + const QList<QQmlJSScope::ConstPtr> &pureTypes = m_visitor->pureQmlTypes(inlineComponent); + Q_ASSERT(!pureTypes.empty()); + const QQmlJSScope::ConstPtr &root = pureTypes.front(); + if (isComponent(root)) { + compiledTypes.emplaceBack(); // create empty type + const auto compile = [&](QmltcType ¤t, const QQmlJSScope::ConstPtr &type) { + generator.generate_initCodeForTopLevelComponent(current, type); + }; + compileType(compiledTypes.back(), root, compile); + } else { + const auto compile = [this](QmltcType ¤t, const QQmlJSScope::ConstPtr &type) { + compileTypeElements(current, type); + }; + + for (const auto &type : pureTypes) { + Q_ASSERT(type->scopeType() == QQmlSA::ScopeType::QMLScope); + compiledTypes.emplaceBack(); // create empty type + compileType(compiledTypes.back(), type, compile); + } + } + } + + if (hasErrors()) + return; + + QmltcProgram program; + program.url = m_url; + program.cppPath = m_info.outputCppFile; + program.hPath = m_info.outputHFile; + program.outNamespace = m_info.outputNamespace; + program.exportMacro = m_info.exportMacro; + program.compiledTypes = compiledTypes; + program.includes = m_visitor->cppIncludeFiles(); + if (!m_info.exportMacro.isEmpty() && !m_info.exportInclude.isEmpty()) + program.includes += (m_info.exportInclude); + program.urlMethod = urlMethod; + + QmltcOutput out; + QmltcOutputWrapper code(out); + QmltcCodeWriter::write(code, program); +} + +void QmltcCompiler::compileUrlMethod(QmltcMethod &urlMethod, const QString &urlMethodName) +{ + urlMethod.name = urlMethodName; + urlMethod.returnType = u"const QUrl&"_s; + urlMethod.body << u"static QUrl url {QStringLiteral(\"qrc:%1\")};"_s.arg(m_info.resourcePath); + urlMethod.body << u"return url;"_s; + urlMethod.declarationPrefixes << u"static"_s; + urlMethod.modifiers << u"noexcept"_s; +} + +void QmltcCompiler::compileType( + QmltcType ¤t, const QQmlJSScope::ConstPtr &type, + std::function<void(QmltcType &, const QQmlJSScope::ConstPtr &)> compileElements) +{ + Q_ASSERT(!type->internalName().isEmpty()); + current.cppType = type->internalName(); + Q_ASSERT(!type->baseType()->internalName().isEmpty()); + const QString baseClass = type->baseType()->internalName(); + + const auto rootType = m_visitor->result(); + const InlineComponentOrDocumentRootName name = type->enclosingInlineComponentName(); + QQmlJSScope::ConstPtr inlineComponentType = m_visitor->inlineComponent(name); + Q_ASSERT(inlineComponentType); + const bool documentRoot = (type == rootType); + const bool inlineComponent = type->isInlineComponent(); + const bool isAnonymous = !documentRoot || type->internalName().at(0).isLower(); + const bool isSingleton = type->isSingleton(); + + QmltcCodeGenerator generator { m_url, m_visitor }; + + current.baseClasses = { baseClass }; + if (!documentRoot) { + // make document root a friend to allow it to access init and endInit + const QString rootInternalName = + m_visitor->inlineComponent(type->enclosingInlineComponentName())->internalName(); + if (rootInternalName != current.cppType) // avoid GCC13 warning on self-befriending + current.otherCode << "friend class %1;"_L1.arg(rootInternalName); + } + if (documentRoot || inlineComponent) { + auto name = type->inlineComponentName() + ? InlineComponentOrDocumentRootName(*type->inlineComponentName()) + : InlineComponentOrDocumentRootName(RootDocumentNameType()); + // make QQmltcObjectCreationBase<DocumentRoot> a friend to allow it to + // be created for the root object + current.otherCode << u"friend class QQmltcObjectCreationBase<%1>;"_s.arg( + inlineComponentType->internalName()); + // generate typeCount for all components (root + inlineComponents) + QmltcMethod typeCountMethod; + typeCountMethod.name = QmltcCodeGenerator::typeCountName; + typeCountMethod.returnType = u"uint"_s; + typeCountMethod.body << u"return " + generator.generate_typeCount(name) + u";"; + current.typeCount = typeCountMethod; + } else { + // make an immediate parent a friend since that parent + // would create the object through a non-public constructor + const auto realQmlScope = [](const QQmlJSScope::ConstPtr &scope) { + if (scope->isArrayScope()) + return scope->parentScope(); + return scope; + }; + + const auto& realScope = realQmlScope(type->parentScope()); + if (realScope != rootType) { + current.otherCode << u"friend class %1;"_s.arg(realScope->internalName()); + } + } + + // make QQmltcObjectCreationHelper a friend of every type since it provides + // useful helper methods for all types + current.otherCode << u"friend class QT_PREPEND_NAMESPACE(QQmltcObjectCreationHelper);"_s; + + current.mocCode = { + u"Q_OBJECT"_s, + // Note: isAnonymous holds for non-root types in the document as well + type->isInlineComponent() ? (u"QML_NAMED_ELEMENT(%1)"_s.arg(*type->inlineComponentName())) + : (isAnonymous ? u"QML_ANONYMOUS"_s : u"QML_ELEMENT"_s), + }; + + // add special member functions + current.baselineCtor.access = QQmlJSMetaMethod::Protected; + if (documentRoot || inlineComponent || isSingleton) { + current.externalCtor.access = QQmlJSMetaMethod::Public; + } else { + current.externalCtor.access = QQmlJSMetaMethod::Protected; + } + current.init.access = QQmlJSMetaMethod::Protected; + current.beginClass.access = QQmlJSMetaMethod::Protected; + current.endInit.access = QQmlJSMetaMethod::Protected; + current.setComplexBindings.access = QQmlJSMetaMethod::Protected; + current.completeComponent.access = QQmlJSMetaMethod::Protected; + current.finalizeComponent.access = QQmlJSMetaMethod::Protected; + current.handleOnCompleted.access = QQmlJSMetaMethod::Protected; + + current.propertyInitializer.name = u"PropertyInitializer"_s; + current.propertyInitializer.constructor.access = QQmlJSMetaMethod::Public; + current.propertyInitializer.constructor.name = current.propertyInitializer.name; + current.propertyInitializer.constructor.parameterList = { + QmltcVariable(u"%1&"_s.arg(current.cppType), u"component"_s) + }; + current.propertyInitializer.component.cppType = current.cppType + u"&"; + current.propertyInitializer.component.name = u"component"_s; + current.propertyInitializer.initializedCache.cppType = u"QSet<QString>"_s; + current.propertyInitializer.initializedCache.name = u"initializedCache"_s; + + current.baselineCtor.name = current.cppType; + current.externalCtor.name = current.cppType; + current.init.name = u"QML_init"_s; + current.init.returnType = u"QQmlRefPointer<QQmlContextData>"_s; + current.beginClass.name = u"QML_beginClass"_s; + current.beginClass.returnType = u"void"_s; + current.endInit.name = u"QML_endInit"_s; + current.endInit.returnType = u"void"_s; + current.setComplexBindings.name = u"QML_setComplexBindings"_s; + current.setComplexBindings.returnType = u"void"_s; + current.completeComponent.name = u"QML_completeComponent"_s; + current.completeComponent.returnType = u"void"_s; + current.finalizeComponent.name = u"QML_finalizeComponent"_s; + current.finalizeComponent.returnType = u"void"_s; + current.handleOnCompleted.name = u"QML_handleOnCompleted"_s; + current.handleOnCompleted.returnType = u"void"_s; + QmltcVariable creator(u"QQmltcObjectCreationHelper*"_s, u"creator"_s); + QmltcVariable engine(u"QQmlEngine*"_s, u"engine"_s); + QmltcVariable parent(u"QObject*"_s, u"parent"_s, u"nullptr"_s); + QmltcVariable initializedCache( + u"[[maybe_unused]] const QSet<QString>&"_s, + u"initializedCache"_s, + u"{}"_s + ); + QmltcVariable ctxtdata(u"const QQmlRefPointer<QQmlContextData>&"_s, u"parentContext"_s); + QmltcVariable finalizeFlag(u"bool"_s, u"canFinalize"_s); + current.baselineCtor.parameterList = { parent }; + current.endInit.parameterList = { creator, engine }; + current.setComplexBindings.parameterList = { creator, engine, initializedCache }; + current.handleOnCompleted.parameterList = { creator }; + + if (documentRoot || inlineComponent) { + const QmltcVariable initializer( + u"[[maybe_unused]] qxp::function_ref<void(%1&)>"_s.arg(current.propertyInitializer.name), + u"initializer"_s, + u"[](%1&){}"_s.arg(current.propertyInitializer.name)); + + current.init.parameterList = { creator, engine, ctxtdata, finalizeFlag, initializer }; + current.beginClass.parameterList = { creator, finalizeFlag }; + current.completeComponent.parameterList = { creator, finalizeFlag }; + current.finalizeComponent.parameterList = { creator, finalizeFlag }; + + compileRequiredPropertiesBundle(current, type, m_typeResolver); + + if (current.requiredPropertiesBundle) { + QmltcVariable bundle{ + u"const %1&"_s.arg(current.requiredPropertiesBundle->name), + u"requiredPropertiesBundle"_s, + }; + current.externalCtor.parameterList = { engine, bundle, parent, initializer }; + } else { + current.externalCtor.parameterList = { engine, parent, initializer }; + } + } else { + current.externalCtor.parameterList = { creator, engine, parent }; + current.init.parameterList = { creator, engine, ctxtdata }; + current.beginClass.parameterList = { creator }; + current.completeComponent.parameterList = { creator }; + current.finalizeComponent.parameterList = { creator }; + } + + current.externalCtor.initializerList = { current.baselineCtor.name + u"(" + parent.name + + u")" }; + if (QQmlJSUtils::hasCompositeBase(type)) { + // call parent's (QML type's) basic ctor from this. that one will take + // care about QObject::setParent() + current.baselineCtor.initializerList = { baseClass + u"(" + parent.name + u")" }; + } else { + // default call to ctor is enough, but QQml_setParent_noEvent() is + // needed (note: faster? version of QObject::setParent()) + current.baselineCtor.body << u"QQml_setParent_noEvent(this, " + parent.name + u");"; + } + + // compilation stub: + current.externalCtor.body << u"Q_UNUSED(engine)"_s; + if (documentRoot || inlineComponent) { + compileRootExternalConstructorBody(current, type); + } else { + current.externalCtor.body << u"// not document root:"_s; + // just call init, we don't do any setup here otherwise + current.externalCtor.body << current.init.name + + u"(creator, engine, QQmlData::get(parent)->outerContext);"; + } + + if (isSingleton) { + // see https://doc.qt.io/qt-6/qqmlengine.html#QML_SINGLETON for context + current.mocCode.append(u"QML_SINGLETON"_s); + auto &staticCreate = current.staticCreate.emplace(); + staticCreate.comments + << u"Used by the engine for singleton creation."_s + << u"See also \\l {https://doc.qt.io/qt-6/qqmlengine.html#QML_SINGLETON}."_s; + staticCreate.type = QQmlJSMetaMethodType::StaticMethod; + staticCreate.access = QQmlJSMetaMethod::Public; + staticCreate.name = u"create"_s; + staticCreate.returnType = u"%1 *"_s.arg(current.cppType); + QmltcVariable jsEngine(u"QJSEngine*"_s, u"jsEngine"_s); + staticCreate.parameterList = { engine, jsEngine }; + staticCreate.body << u"Q_UNUSED(jsEngine);"_s + << u"%1 *result = new %1(engine, nullptr);"_s.arg(current.cppType) + << u"return result;"_s; + } + auto postponedQmlContextSetup = generator.generate_initCode(current, type); + generator.generate_endInitCode(current, type); + generator.generate_setComplexBindingsCode(current, type); + generator.generate_beginClassCode(current, type); + generator.generate_completeComponentCode(current, type); + generator.generate_finalizeComponentCode(current, type); + generator.generate_handleOnCompletedCode(current, type); + + compileElements(current, type); +} + +template<typename Iterator> +static Iterator partitionBindings(Iterator first, Iterator last) +{ + // NB: the code generator cares about script bindings being processed at a + // later point, so we should sort or partition the range. we do a stable + // partition since the relative order of binding evaluation affects the UI + return std::stable_partition(first, last, [](const QQmlJSMetaPropertyBinding &b) { + // we want complex bindings to be at the end, so do the negation + return !QmltcCompiler::isComplexBinding(b); + }); +} + +// Populates the propertyInitializer of the current type based on the +// available properties. +// +// A propertyInitializer is a generated class that provides a +// restricted interface that only allows setting property values and +// internally keep tracks of which properties where actually set, +// intended to be used to allow the user to set up the initial values +// when creating an instance of a component. +// +// For each property of the current type that is known, is not private +// and is writable, a setter method is generated. +// Each setter method knows how to set a specific property, so as to +// provide a strongly typed interface to property setting, as if the +// relevant C++ type was used directly. +// +// Each setter uses the write method for the proprerty when available +// and otherwise falls back to a the more generic +// `QObject::setProperty` for properties where a WRITE method is not +// available or in scope. +static void compilePropertyInitializer(QmltcType ¤t, const QQmlJSScope::ConstPtr &type) { + static auto isFromExtension = [](const QQmlJSMetaProperty &property, const QQmlJSScope::ConstPtr &scope) { + return scope->ownerOfProperty(scope, property.propertyName()).extensionSpecifier != QQmlJSScope::NotExtension; + }; + + current.propertyInitializer.constructor.initializerList << u"component{component}"_s; + + auto properties = type->properties().values(); + for (auto& property: properties) { + if (property.index() == -1) continue; + if (property.isPrivate()) continue; + if (!property.isWritable() && !qIsReferenceTypeList(property)) continue; + + const QString name = property.propertyName(); + + current.propertyInitializer.propertySetters.emplace_back(); + auto& compiledSetter = current.propertyInitializer.propertySetters.back(); + + compiledSetter.userVisible = true; + compiledSetter.returnType = u"void"_s; + compiledSetter.name = QmltcPropertyData(property).write; + + if (qIsReferenceTypeList(property)) { + compiledSetter.parameterList.emplaceBack( + QQmlJSUtils::constRefify(u"QList<%1*>"_s.arg(property.type()->valueType()->internalName())), + name + u"_", QString() + ); + } else { + compiledSetter.parameterList.emplaceBack( + QQmlJSUtils::constRefify(getUnderlyingType(property)), name + u"_", QString() + ); + } + + if (qIsReferenceTypeList(property)) { + compiledSetter.body << u"QQmlListReference list_ref_(&%1, \"%2\");"_s.arg( + current.propertyInitializer.component.name, name + ); + compiledSetter.body << u"list_ref_.clear();"_s; + compiledSetter.body << u"for (const auto& list_item_ : %1_)"_s.arg(name); + compiledSetter.body << u" list_ref_.append(list_item_);"_s; + } else if ( + QQmlJSUtils::bindablePropertyHasDefaultAccessor(property, QQmlJSUtils::PropertyAccessor_Write) + ) { + compiledSetter.body << u"%1.%2().setValue(%3_);"_s.arg( + current.propertyInitializer.component.name, property.bindable(), name); + } else if (type->hasOwnProperty(name)) { + compiledSetter.body << u"%1.%2(%3_);"_s.arg( + current.propertyInitializer.component.name, QmltcPropertyData(property).write, name); + } else if (property.write().isEmpty() || isFromExtension(property, type)) { + // We can end here if a WRITE method is not available or + // if the method is available but not in this scope, so + // that we fallback to the string-based setters.. + // + // For example, types that makes use of QML_EXTENDED + // types, will have the extension types properties + // available and with a WRITE method, but the WRITE method + // will not be available to the extended type, from C++, + // as the type does not directly inherit from the + // extension type. + // + // We specifically scope `setProperty` to `QObject` as + // certain types might have shadowed the method. + // For example, in QtQuick, some types have a property + // called `property` with a `setProperty` WRITE method + // that will produce the shadowing. + compiledSetter.body << u"%1.QObject::setProperty(\"%2\", QVariant::fromValue(%2_));"_s.arg( + current.propertyInitializer.component.name, name); + } else { + compiledSetter.body << u"%1.%2(%3_);"_s.arg( + current.propertyInitializer.component.name, property.write(), name); + } + + compiledSetter.body << u"%1.insert(\"%2\");"_s.arg( + current.propertyInitializer.initializedCache.name, name); + } +} + +void QmltcCompiler::compileTypeElements(QmltcType ¤t, const QQmlJSScope::ConstPtr &type) +{ + // compile components of a type: + // - enums + // - properties + // - methods + // - bindings + + const auto enums = type->ownEnumerations(); + current.enums.reserve(enums.size()); + for (auto it = enums.begin(); it != enums.end(); ++it) + compileEnum(current, it.value()); + + auto properties = type->ownProperties().values(); + current.properties.reserve(properties.size()); + // Note: index() is the (future) meta property index, so make sure given + // properties are ordered by that index before compiling + std::sort(properties.begin(), properties.end(), + [](const QQmlJSMetaProperty &x, const QQmlJSMetaProperty &y) { + return x.index() < y.index(); + }); + for (const QQmlJSMetaProperty &p : std::as_const(properties)) { + if (p.index() == -1) { + recordError(type->sourceLocation(), + u"Internal error: property '%1' has incomplete information"_s.arg( + p.propertyName())); + continue; + } + if (p.isAlias()) { + compileAlias(current, p, type); + } else { + compileProperty(current, p, type); + } + } + + const auto methods = type->ownMethods(); + for (const QQmlJSMetaMethod &m : methods) + compileMethod(current, m, type); + + auto bindings = type->ownPropertyBindingsInQmlIROrder(); + partitionBindings(bindings.begin(), bindings.end()); + + compilePropertyInitializer(current, type); + compileBinding(current, bindings.begin(), bindings.end(), type, { type }); +} + +void QmltcCompiler::compileEnum(QmltcType ¤t, const QQmlJSMetaEnum &e) +{ + const auto intValues = e.values(); + QStringList values; + values.reserve(intValues.size()); + std::transform(intValues.cbegin(), intValues.cend(), std::back_inserter(values), + [](int x) { return QString::number(x); }); + + // structure: (C++ type name, enum keys, enum values, MOC line) + current.enums.emplaceBack(e.name(), e.keys(), std::move(values), + u"Q_ENUM(%1)"_s.arg(e.name())); +} + +static QList<QmltcVariable> +compileMethodParameters(const QList<QQmlJSMetaParameter> ¶meterInfos, bool allowUnnamed = false) +{ + QList<QmltcVariable> parameters; + const auto size = parameterInfos.size(); + parameters.reserve(size); + for (qsizetype i = 0; i < size; ++i) { + const auto &p = parameterInfos[i]; + Q_ASSERT(p.type()); // assume verified + QString name = p.name(); + Q_ASSERT(allowUnnamed || !name.isEmpty()); // assume verified + if (name.isEmpty() && allowUnnamed) + name = u"unnamed_" + QString::number(i); + + QString internalName; + const QQmlJSScope::AccessSemantics semantics = p.type()->accessSemantics(); + + switch (semantics) { + case QQmlJSScope::AccessSemantics::Reference: + if (p.typeQualifier() == QQmlJSMetaParameter::Const) + internalName = u"const "_s; + internalName += u"%1*"_s.arg(p.type()->internalName()); + break; + case QQmlJSScope::AccessSemantics::Value: + case QQmlJSScope::AccessSemantics::Sequence: + internalName = u"passByConstRefOrValue<%1>"_s.arg(p.type()->internalName()); + break; + case QQmlJSScope::AccessSemantics::None: + Q_ASSERT(false); // or maybe print an error message + } + parameters.emplaceBack(internalName, name, QString()); + } + return parameters; +} + +static QString figureReturnType(const QQmlJSMetaMethod &m) +{ + const bool isVoidMethod = + m.returnTypeName() == u"void" || m.methodType() == QQmlJSMetaMethodType::Signal; + Q_ASSERT(isVoidMethod || m.returnType()); + QString type; + if (isVoidMethod) { + type = u"void"_s; + } else { + type = m.returnType()->augmentedInternalName(); + } + return type; +} + +void QmltcCompiler::compileMethod(QmltcType ¤t, const QQmlJSMetaMethod &m, + const QQmlJSScope::ConstPtr &owner) +{ + const auto returnType = figureReturnType(m); + + const QList<QmltcVariable> compiledParams = compileMethodParameters(m.parameters()); + const auto methodType = m.methodType(); + + QStringList code; + if (methodType != QQmlJSMetaMethodType::Signal) { + QmltcCodeGenerator urlGenerator { m_url, m_visitor }; + QmltcCodeGenerator::generate_callExecuteRuntimeFunction( + &code, urlGenerator.urlMethodName() + u"()", + owner->ownRuntimeFunctionIndex(m.jsFunctionIndex()), u"this"_s, returnType, + compiledParams); + } + + QmltcMethod compiled {}; + compiled.returnType = returnType; + compiled.name = m.methodName(); + compiled.parameterList = std::move(compiledParams); + compiled.body = std::move(code); + compiled.type = methodType; + compiled.access = m.access(); + if (methodType != QQmlJSMetaMethodType::Signal) { + compiled.declarationPrefixes << u"Q_INVOKABLE"_s; + compiled.userVisible = m.access() == QQmlJSMetaMethod::Public; + } else { + compiled.userVisible = !m.isImplicitQmlPropertyChangeSignal(); + } + current.functions.emplaceBack(compiled); +} + +/*! \internal + Compiles an extra set of methods for Lists, that makes manipulating lists easier from C++ + for the user. +*/ +void QmltcCompiler::compileExtraListMethods(QmltcType ¤t, const QQmlJSMetaProperty &p) +{ + QmltcPropertyData data(p); + const QString valueType = p.type()->valueType()->internalName() + u'*'; + const QString variableName = data.read + u"()"_s; + const QStringList ownershipWarning = { + u"\\note {This method does not change the ownership of its argument."_s, + u"The caller is responsible for setting the argument's \\c {QObject::parent} or"_s, + u"for ensuring that the argument lives long enough."_s, + u"For example, an argument created with \\c {createObject()} that has no parent"_s, + u"will eventually be garbage-collected, leaving a dangling pointer.}"_s + }; + + // generate append() sugar for users + { + QmltcMethod append{}; + append.comments.emplaceBack(u"\\brief Append an element to %1."_s.arg(data.read)); + append.comments << ownershipWarning; + append.returnType = u"void"_s; + append.name = u"%1Append"_s.arg(data.read); + append.parameterList.emplaceBack(valueType, u"toBeAppended"_s); + + append.body << u"auto q_qmltc_localList = %1;"_s.arg(variableName); + append.body + << u"q_qmltc_localList.append(std::addressof(q_qmltc_localList), toBeAppended);"_s; + // append.body << u"Q_EMIT %1();"_s.arg(data.notify); // uncomment this when QTBUG-106587 is + // resolved + append.userVisible = true; + current.functions.emplaceBack(append); + } + + // generate count() sugar for users + { + QmltcMethod count{}; + count.comments.emplaceBack(u"\\brief Number of elements in %1."_s.arg(data.read)); + count.returnType = u"int"_s; + count.name = u"%1Count"_s.arg(data.read); + + count.body << u"auto q_qmltc_localList = %1;"_s.arg(variableName); + count.body << u"int result = q_qmltc_localList.count(std::addressof(q_qmltc_localList));"_s; + count.body << u"return result;"_s; + count.userVisible = true; + current.functions.emplaceBack(count); + } + + // generate at() sugar for users + { + QmltcMethod at{}; + at.comments.emplaceBack(u"\\brief Access an element in %1."_s.arg(data.read)); + at.returnType = valueType; + at.name = u"%1At"_s.arg(data.read); + at.parameterList.emplaceBack(u"qsizetype"_s, u"position"_s, QString()); + + at.body << u"auto q_qmltc_localList = %1;"_s.arg(variableName); + at.body << u"auto result = q_qmltc_localList.at(std::addressof(q_qmltc_localList), position);"_s; + at.body << u"return result;"_s; + at.userVisible = true; + current.functions.emplaceBack(at); + } + + // generate clear() sugar for users + { + QmltcMethod clear{}; + clear.comments.emplaceBack(u"\\brief Clear %1."_s.arg(data.read)); + clear.returnType = u"void"_s; + clear.name = u"%1Clear"_s.arg(data.read); + + clear.body << u"auto q_qmltc_localList = %1;"_s.arg(variableName); + clear.body << u"q_qmltc_localList.clear(std::addressof(q_qmltc_localList));"_s; + // clear.body << u"Q_EMIT %1();"_s.arg(data.notify); // uncomment this when QTBUG-106587 is + // resolved + clear.userVisible = true; + current.functions.emplaceBack(clear); + } + + // generate replace() sugar for users + { + QmltcMethod replace{}; + replace.comments.emplaceBack(u"\\brief Replace an element in %1."_s.arg(data.read)); + replace.comments << ownershipWarning; + replace.returnType = u"void"_s; + replace.name = u"%1Replace"_s.arg(data.read); + replace.parameterList.emplaceBack(u"qsizetype"_s, u"position"_s, QString()); + replace.parameterList.emplaceBack(valueType, u"element"_s, + QString()); + + replace.body << u"auto q_qmltc_localList = %1;"_s.arg(variableName); + replace.body + << u"q_qmltc_localList.replace(std::addressof(q_qmltc_localList), position, element);"_s; + // replace.body << u"Q_EMIT %1();"_s.arg(data.notify); // uncomment this when QTBUG-106587 + // is resolved + replace.userVisible = true; + current.functions.emplaceBack(replace); + } + + // generate removeLast() sugar for users + { + QmltcMethod removeLast{}; + removeLast.comments.emplaceBack(u"\\brief Remove the last element in %1."_s.arg(data.read)); + removeLast.returnType = u"void"_s; + removeLast.name = u"%1RemoveLast"_s.arg(data.read); + + removeLast.body << u"auto q_qmltc_localList = %1;"_s.arg(variableName); + removeLast.body << u"q_qmltc_localList.removeLast(std::addressof(q_qmltc_localList));"_s; + // removeLast.body << u"Q_EMIT %1();"_s.arg(data.notify); // uncomment this when + // QTBUG-106587 is resolved + + removeLast.userVisible = true; + current.functions.emplaceBack(removeLast); + } +} + +void QmltcCompiler::compileProperty(QmltcType ¤t, const QQmlJSMetaProperty &p, + const QQmlJSScope::ConstPtr &owner) +{ + Q_ASSERT(!p.isAlias()); // will be handled separately + Q_ASSERT(p.type()); + + const QString name = p.propertyName(); + const QString variableName = u"m_" + name; + const QString underlyingType = getUnderlyingType(p); + if (qIsReferenceTypeList(p)) { + const QString storageName = variableName + u"_storage"; + current.variables.emplaceBack( + u"QList<" + p.type()->valueType()->internalName() + u"*>", storageName, + QString()); + current.baselineCtor.initializerList.emplaceBack(variableName + u"(" + underlyingType + + u"(this, std::addressof(" + storageName + + u")))"); + compileExtraListMethods(current, p); + } + + // along with property, also add relevant moc code, so that we can use the + // property in Qt/QML contexts + QStringList mocPieces; + mocPieces.reserve(10); + mocPieces << underlyingType << name; + + QmltcPropertyData compilationData(p); + + // 1. add setter and getter + // If p.isList(), it's a QQmlListProperty. Then you can write the underlying list through + // the QQmlListProperty object retrieved with the getter. Setting it would make no sense. + if (p.isWritable() && !qIsReferenceTypeList(p)) { + QmltcMethod setter {}; + setter.returnType = u"void"_s; + setter.name = compilationData.write; + // QmltcVariable + setter.parameterList.emplaceBack(QQmlJSUtils::constRefify(underlyingType), name + u"_", + u""_s); + setter.body << variableName + u".setValue(" + name + u"_);"; + setter.body << u"Q_EMIT " + compilationData.notify + u"();"; + setter.userVisible = true; + current.functions.emplaceBack(setter); + mocPieces << u"WRITE"_s << setter.name; + } + + QmltcMethod getter {}; + getter.returnType = underlyingType; + getter.name = compilationData.read; + getter.body << u"return " + variableName + u".value();"; + getter.userVisible = true; + current.functions.emplaceBack(getter); + mocPieces << u"READ"_s << getter.name; + + // 2. add bindable + if (!qIsReferenceTypeList(p)) { + QmltcMethod bindable {}; + bindable.returnType = u"QBindable<" + underlyingType + u">"; + bindable.name = compilationData.bindable; + bindable.body << u"return QBindable<" + underlyingType + u">(std::addressof(" + variableName + + u"));"; + bindable.userVisible = true; + current.functions.emplaceBack(bindable); + mocPieces << u"BINDABLE"_s << bindable.name; + } + + // 3. add/check notify (actually, this is already done inside QmltcVisitor) + + if (owner->isPropertyRequired(name)) + mocPieces << u"REQUIRED"_s; + + // 4. add moc entry + // e.g. Q_PROPERTY(QString p READ getP WRITE setP BINDABLE bindableP) + current.mocCode << u"Q_PROPERTY(" + mocPieces.join(u" "_s) + u")"; + + // 5. add extra moc entry if this property is marked default + if (name == owner->defaultPropertyName()) + current.mocCode << u"Q_CLASSINFO(\"DefaultProperty\", \"%1\")"_s.arg(name); + + // structure: (C++ type name, name, C++ class name, C++ signal name) + current.properties.emplaceBack(underlyingType, variableName, current.cppType, + compilationData.notify); +} + +/*! + * \internal + * + * Models one step of the alias resolution. If the current alias to be resolved + * points to \c {x.y.z} and that \c {x.y} is already resolved, then this struct + * contains the information on how to obtain the \c {z} part from \c {x.y}. + */ +struct AliasResolutionFrame +{ + /*! + * \internal + * + * Placeholder for the current resolved state. It is replaced later with + * the result from previous resolutions from the \c QStack<AliasResolutionFrame>. + * + * \sa unpackFrames() + */ + static QString inVar; + + /*! + * \internal + * + * Steps to access this value as a list of C++ statements, to be used in + * conjunction with \c {epilogue}. + */ + QStringList prologue; + + /*! + * \internal + * + * Steps to finish the statements of the \c prologue (e.g. closing brackets). + */ + QStringList epilogue; + + /*! + * \internal + * + * Instructions on how to write the property, after it was loaded with the + * instructions from \c prologue. Has to happen before \c epilogue. + */ + QStringList epilogueForWrite; + + /*! + * \internal + * + * Name of the variable holding the result of this resolution step, to be + * used in the following resolution steps. + */ + QString outVar; +}; +// special string replaced by outVar of the previous frame +QString AliasResolutionFrame::inVar = QStringLiteral("__QMLTC_ALIAS_FRAME_INPUT_VAR__"); + +/*! + * \internal + * + * Process the frames by replacing the placeholder \c invar + * used in \c epilogueForWrite and \c prologue with the result + * obtained from the previous frame. + */ +static void unpackFrames(QStack<AliasResolutionFrame> &frames) +{ + if (frames.size() < 2) + return; + + // assume first frame is fine + auto prev = frames.begin(); + for (auto it = std::next(prev); it != frames.end(); ++it, ++prev) { + for (QString &line : it->prologue) + line.replace(AliasResolutionFrame::inVar, prev->outVar); + for (QString &line : it->epilogueForWrite) + line.replace(AliasResolutionFrame::inVar, prev->outVar); + it->outVar.replace(AliasResolutionFrame::inVar, prev->outVar); + } +} + +template<typename Projection> +static QStringList joinFrames(const QStack<AliasResolutionFrame> &frames, Projection project) +{ + QStringList joined; + for (const AliasResolutionFrame &frame : frames) + joined += project(frame); + return joined; +} + +void QmltcCompiler::compileAlias(QmltcType ¤t, const QQmlJSMetaProperty &alias, + const QQmlJSScope::ConstPtr &owner) +{ + const QString aliasName = alias.propertyName(); + Q_ASSERT(!aliasName.isEmpty()); + + QStringList aliasExprBits = alias.aliasExpression().split(u'.'); + Q_ASSERT(!aliasExprBits.isEmpty()); + + QStack<AliasResolutionFrame> frames; + + QQmlJSUtils::AliasResolutionVisitor aliasVisitor; + qsizetype i = 0; + aliasVisitor.reset = [&]() { + frames.clear(); + i = 0; // we use it in property processing + + // first frame is a dummy one: + frames.push( + AliasResolutionFrame { QStringList(), QStringList(), QStringList(), u"this"_s }); + }; + aliasVisitor.processResolvedId = [&](const QQmlJSScope::ConstPtr &type) { + Q_ASSERT(type); + if (owner != type) { // cannot start at `this`, need to fetch object through context + const int id = m_visitor->runtimeId(type); + Q_ASSERT(id >= 0); // since the type is found by id, it must have an id + + AliasResolutionFrame queryIdFrame {}; + Q_ASSERT(frames.top().outVar == u"this"_s); // so inVar would be "this" as well + queryIdFrame.prologue << u"auto context = %1::q_qmltc_thisContext;"_s.arg( + owner->internalName()); + + // doing the above allows us to lookup id object by index (fast) + queryIdFrame.outVar = u"alias_objectById_" + aliasExprBits.front(); // unique enough + const QString cppType = (m_visitor->qmlComponentIndex(type) == -1) + ? type->internalName() + : u"QQmlComponent"_s; + queryIdFrame.prologue << u"auto " + queryIdFrame.outVar + u" = static_cast<" + cppType + + u"*>(context->idValue(" + QString::number(id) + u"));"; + queryIdFrame.prologue << u"Q_ASSERT(" + queryIdFrame.outVar + u");"; + + frames.push(queryIdFrame); + } + }; + aliasVisitor.processResolvedProperty = [&](const QQmlJSMetaProperty &p, + const QQmlJSScope::ConstPtr &owner) { + AliasResolutionFrame queryPropertyFrame {}; + + auto [extensionPrologue, extensionAccessor, extensionEpilogue] = + QmltcCodeGenerator::wrap_extensionType( + owner, p, + QmltcCodeGenerator::wrap_privateClass(AliasResolutionFrame::inVar, p)); + QString inVar = extensionAccessor; + queryPropertyFrame.prologue += extensionPrologue; + if (p.type()->accessSemantics() == QQmlJSScope::AccessSemantics::Value) { + // we need to read the property to a local variable and then + // write the updated value once the actual operation is done + const QString aliasVar = u"alias_" + QString::number(i); // should be fairly unique + ++i; + queryPropertyFrame.prologue + << u"auto " + aliasVar + u" = " + inVar + u"->" + p.read() + u"();"; + queryPropertyFrame.epilogueForWrite + << inVar + u"->" + p.write() + u"(" + aliasVar + u");"; + // NB: since accessor becomes a value type, wrap it into an + // addressof operator so that we could access it as a pointer + inVar = QmltcCodeGenerator::wrap_addressof(aliasVar); // reset + } else { + inVar += u"->" + p.read() + u"()"; // update + } + queryPropertyFrame.outVar = inVar; + queryPropertyFrame.epilogue += extensionEpilogue; + + frames.push(queryPropertyFrame); + }; + + QQmlJSUtils::ResolvedAlias result = + QQmlJSUtils::resolveAlias(m_typeResolver, alias, owner, aliasVisitor); + Q_ASSERT(result.kind != QQmlJSUtils::AliasTarget_Invalid); + + unpackFrames(frames); + + if (result.kind == QQmlJSUtils::AliasTarget_Property) { + // we don't need the last frame here + frames.pop(); + + // instead, add a custom frame + AliasResolutionFrame customFinalFrame {}; + auto [extensionPrologue, extensionAccessor, extensionEpilogue] = + QmltcCodeGenerator::wrap_extensionType( + result.owner, result.property, + QmltcCodeGenerator::wrap_privateClass(frames.top().outVar, + result.property)); + customFinalFrame.prologue = extensionPrologue; + customFinalFrame.outVar = extensionAccessor; + customFinalFrame.epilogue = extensionEpilogue; + frames.push(customFinalFrame); + } + + const QString latestAccessor = frames.top().outVar; + const QStringList prologue = + joinFrames(frames, [](const AliasResolutionFrame &frame) { return frame.prologue; }); + const QStringList epilogue = + joinFrames(frames, [](const AliasResolutionFrame &frame) { return frame.epilogue; }); + const QString underlyingType = (result.kind == QQmlJSUtils::AliasTarget_Property) + ? getUnderlyingType(result.property) + : result.owner->internalName() + u" *"; + + QStringList mocLines; + mocLines.reserve(10); + mocLines << underlyingType << aliasName; + + QmltcPropertyData compilationData(aliasName); + // 1. add setter and getter + QmltcMethod getter {}; + getter.returnType = underlyingType; + getter.name = compilationData.read; + getter.body += prologue; + if (result.kind == QQmlJSUtils::AliasTarget_Property) { + if (QString read = result.property.read(); !read.isEmpty() + && !QQmlJSUtils::bindablePropertyHasDefaultAccessor( + result.property, QQmlJSUtils::PropertyAccessor_Read)) { + getter.body << u"return %1->%2();"_s.arg(latestAccessor, read); + } else { // use QObject::property() as a fallback when read method is unknown + getter.body << u"return qvariant_cast<%1>(%2->property(\"%3\"));"_s.arg( + underlyingType, latestAccessor, result.property.propertyName()); + } + } else { // AliasTarget_Object + getter.body << u"return " + latestAccessor + u";"; + } + getter.body += epilogue; + getter.userVisible = true; + current.functions.emplaceBack(getter); + mocLines << u"READ"_s << getter.name; + + if (result.property.isWritable()) { + Q_ASSERT(result.kind == QQmlJSUtils::AliasTarget_Property); // property is invalid otherwise + QmltcMethod setter {}; + setter.returnType = u"void"_s; + setter.name = compilationData.write; + + const QString setName = result.property.write(); + QList<QQmlJSMetaMethod> methods = result.owner->methods(setName); + if (methods.isEmpty()) { // when we are compiling the property as well + // QmltcVariable + setter.parameterList.emplaceBack(QQmlJSUtils::constRefify(underlyingType), + aliasName + u"_", u""_s); + } else { + setter.parameterList = compileMethodParameters(methods.at(0).parameters(), + /* allow unnamed = */ true); + } + + setter.body += prologue; + QStringList parameterNames; + parameterNames.reserve(setter.parameterList.size()); + std::transform(setter.parameterList.cbegin(), setter.parameterList.cend(), + std::back_inserter(parameterNames), + [](const QmltcVariable &x) { return x.name; }); + QString commaSeparatedParameterNames = parameterNames.join(u", "_s); + if (!setName.isEmpty() + && !QQmlJSUtils::bindablePropertyHasDefaultAccessor( + result.property, QQmlJSUtils::PropertyAccessor_Write)) { + setter.body << u"%1->%2(%3);"_s.arg(latestAccessor, setName, + commaSeparatedParameterNames); + } else { // use QObject::setProperty() as fallback when write method is unknown + Q_ASSERT(parameterNames.size() == 1); + const QString variantName = u"var_" + aliasName; // fairly unique + setter.body << u"QVariant %1;"_s.arg(variantName); + setter.body << u"%1.setValue(%2);"_s.arg(variantName, commaSeparatedParameterNames); + setter.body << u"%1->setProperty(\"%2\", std::move(%3));"_s.arg( + latestAccessor, result.property.propertyName(), variantName); + } + setter.body += joinFrames( + frames, [](const AliasResolutionFrame &frame) { return frame.epilogueForWrite; }); + setter.body += epilogue; // NB: *after* epilogueForWrite - see prologue construction + setter.userVisible = true; + current.functions.emplaceBack(setter); + mocLines << u"WRITE"_s << setter.name; + } + // 2. add bindable + if (QString bindableName = result.property.bindable(); !bindableName.isEmpty()) { + Q_ASSERT(result.kind == QQmlJSUtils::AliasTarget_Property); // property is invalid otherwise + QmltcMethod bindable {}; + bindable.returnType = u"QBindable<" + underlyingType + u">"; + bindable.name = compilationData.bindable; + bindable.body += prologue; + bindable.body << u"return " + latestAccessor + u"->" + bindableName + u"()" + u";"; + bindable.body += epilogue; + bindable.userVisible = true; + current.functions.emplaceBack(bindable); + mocLines << u"BINDABLE"_s << bindable.name; + } + + // 3. add notify - which is pretty special + // step 1: generate the moc instructions + // mimic the engines behavior: do it even if the notify will never be emitted + if (const QString aliasNotifyName = alias.notify(); !aliasNotifyName.isEmpty()) { + + Q_ASSERT(result.kind == QQmlJSUtils::AliasTarget_Property); // property is invalid otherwise + + mocLines << u"NOTIFY"_s << aliasNotifyName; + } + + // step 2: connect the notifier to the aliased property notifier, if this latter exists + // otherwise, mimic the engines behavior and generate a useless notify + if (const QString notifyName = result.property.notify(); !notifyName.isEmpty()) { + auto notifyFrames = frames; + notifyFrames.pop(); // we don't need the last frame at all in this case + + const QStringList notifyPrologue = joinFrames( + frames, [](const AliasResolutionFrame &frame) { return frame.prologue; }); + const QStringList notifyEpilogue = joinFrames( + frames, [](const AliasResolutionFrame &frame) { return frame.epilogue; }); + + // notify is very special + current.endInit.body << u"{ // alias notify connection:"_s; + current.endInit.body += notifyPrologue; + // TODO: use non-private accessor since signals must exist on the public + // type, not on the private one -- otherwise, you can't connect to a + // private property signal in C++ and so it is useless (hence, use + // public type) + const QString cppType = (m_visitor->qmlComponentIndex(result.owner) == -1) + ? result.owner->internalName() + : u"QQmlComponent"_s; + const QString latestAccessorNonPrivate = notifyFrames.top().outVar; + current.endInit.body << u"QObject::connect(" + latestAccessorNonPrivate + u", &" + cppType + + u"::" + notifyName + u", this, &" + current.cppType + u"::" + + compilationData.notify + u");"; + current.endInit.body += notifyEpilogue; + current.endInit.body << u"}"_s; + } + + if (QString resetName = result.property.reset(); !resetName.isEmpty()) { + Q_ASSERT(result.kind == QQmlJSUtils::AliasTarget_Property); // property is invalid otherwise + QmltcMethod reset {}; + reset.returnType = u"void"_s; + reset.name = compilationData.reset; + reset.body += prologue; + reset.body << latestAccessor + u"->" + resetName + u"()" + u";"; + reset.body += epilogue; + reset.userVisible = true; + current.functions.emplaceBack(reset); + mocLines << u"RESET"_s << reset.name; + } + + // mimic the engines behavior: aliases are never constants + // mocLines << u"CONSTANT"_s; + // mimic the engines behavior: aliases are never stored + mocLines << u"STORED"_s << u"false"_s; + // mimic the engines behavior: aliases are never designable + mocLines << u"DESIGNABLE"_s << u"false"_s; + + // 4. add moc entry + // Q_PROPERTY(QString text READ text WRITE setText BINDABLE bindableText NOTIFY textChanged) + current.mocCode << u"Q_PROPERTY(" + mocLines.join(u" "_s) + u")"; + + // 5. add extra moc entry if this alias is default one + if (aliasName == owner->defaultPropertyName()) { + // Q_CLASSINFO("DefaultProperty", propertyName) + current.mocCode << u"Q_CLASSINFO(\"DefaultProperty\", \"%1\")"_s.arg(aliasName); + } +} + +static QString generate_callCompilationUnit(const QString &urlMethodName) +{ + return u"QQmlEnginePrivate::get(engine)->compilationUnitFromUrl(%1())"_s.arg(urlMethodName); +} + +static std::pair<QQmlJSMetaProperty, int> getMetaPropertyIndex(const QQmlJSScope::ConstPtr &scope, + const QString &propertyName); + +/*! + * \internal + * Helper method used to keep compileBindingByType() readable. + */ +void QmltcCompiler::compileObjectBinding(QmltcType ¤t, + const QQmlJSMetaPropertyBinding &binding, + const QQmlJSScope::ConstPtr &type, + const BindingAccessorData &accessor) +{ + Q_ASSERT(binding.bindingType() == QQmlSA::BindingType::Object); + + const QString &propertyName = binding.propertyName(); + const QQmlJSMetaProperty property = type->property(propertyName); + QQmlJSScope::ConstPtr propertyType = property.type(); + + // NB: object is compiled with compileType(), here just need to use it + auto object = binding.objectType(); + + // Note: despite a binding being set for `accessor`, we use "this" as a + // parent of a created object. Both attached and grouped properties are + // parented by "this", so lifetime-wise we should be fine + const QString qobjectParent = u"this"_s; + + if (!propertyType) { + recordError(binding.sourceLocation(), + u"Binding on property '" + propertyName + u"' of unknown type"); + return; + } + + const auto addObjectBinding = [&](const QString &value) { + if (qIsReferenceTypeList(property)) { + Q_ASSERT(unprocessedListProperty == property || unprocessedListBindings.empty()); + unprocessedListBindings.append(value); + unprocessedListProperty = property; + } else { + QmltcCodeGenerator::generate_assignToProperty(¤t.endInit.body, type, property, + value, accessor.name, true); + } + }; + + // special case of implicit or explicit component: + if (qsizetype index = m_visitor->qmlComponentIndex(object); index >= 0) { + const QString objectName = newSymbol(u"sc"_s); + + const qsizetype creationIndex = m_visitor->creationIndex(object); + + QStringList *block = (creationIndex == -1) ? ¤t.endInit.body : ¤t.init.body; + *block << u"{"_s; + *block << QStringLiteral("auto thisContext = QQmlData::get(%1)->outerContext;") + .arg(qobjectParent); + *block << QStringLiteral("auto %1 = QQmlObjectCreator::createComponent(engine, " + "%2, %3, %4, thisContext);") + .arg(objectName, generate_callCompilationUnit(m_urlMethodName), + QString::number(index), qobjectParent); + *block << QStringLiteral("thisContext->installContext(QQmlData::get(%1), " + "QQmlContextData::OrdinaryObject);") + .arg(objectName); + + // objects wrapped in implicit components do not have visible ids, + // however, explicit components can have an id and that one is going + // to be visible in the common document context + if (creationIndex != -1) { + // explicit component + Q_ASSERT(object->isComposite()); + Q_ASSERT(object->baseType()->internalName() == u"QQmlComponent"_s); + + if (int id = m_visitor->runtimeId(object); id >= 0) { + QString idString = m_visitor->addressableScopes().id(object, object); + if (idString.isEmpty()) + idString = u"<unknown>"_s; + QmltcCodeGenerator::generate_setIdValue(block, u"thisContext"_s, id, objectName, + idString); + } + + const QString creationIndexStr = QString::number(creationIndex); + *block << QStringLiteral("creator->set(%1, %2);").arg(creationIndexStr, objectName); + Q_ASSERT(block == ¤t.init.body); + current.endInit.body << QStringLiteral("auto %1 = creator->get<%2>(%3);") + .arg(objectName, u"QQmlComponent"_s, creationIndexStr); + } + addObjectBinding(objectName); + *block << u"}"_s; + return; + } + + const QString objectName = newSymbol(u"o"_s); + current.init.body << u"auto %1 = new %2(creator, engine, %3);"_s.arg( + objectName, object->internalName(), qobjectParent); + current.init.body << u"creator->set(%1, %2);"_s.arg( + QString::number(m_visitor->creationIndex(object)), objectName); + + // refetch the same object during endInit to set the bindings + current.endInit.body << u"auto %1 = creator->get<%2>(%3);"_s.arg( + objectName, object->internalName(), QString::number(m_visitor->creationIndex(object))); + addObjectBinding(objectName); +} + +/*! + * \internal + * Helper method used to keep compileBindingByType() readable. + */ +void QmltcCompiler::compileValueSourceOrInterceptorBinding(QmltcType ¤t, + const QQmlJSMetaPropertyBinding &binding, + const QQmlJSScope::ConstPtr &type, + const BindingAccessorData &accessor) +{ + Q_ASSERT(binding.bindingType() == QQmlSA::BindingType::ValueSource + || binding.bindingType() == QQmlSA::BindingType::Interceptor); + + const QString &propertyName = binding.propertyName(); + const QQmlJSMetaProperty property = type->property(propertyName); + QQmlJSScope::ConstPtr propertyType = property.type(); + + // NB: object is compiled with compileType(), here just need to use it + QSharedPointer<const QQmlJSScope> object; + if (binding.bindingType() == QQmlSA::BindingType::Interceptor) + object = binding.interceptorType(); + else + object = binding.valueSourceType(); + + // Note: despite a binding being set for `accessor`, we use "this" as a + // parent of a created object. Both attached and grouped properties are + // parented by "this", so lifetime-wise we should be fine + const QString qobjectParent = u"this"_s; + + if (!propertyType) { + recordError(binding.sourceLocation(), + u"Binding on property '" + propertyName + u"' of unknown type"); + return; + } + + auto &objectName = m_uniques[UniqueStringId(current, propertyName)].onAssignmentObjectName; + if (objectName.isEmpty()) { + objectName = u"onAssign_" + propertyName; + + current.init.body << u"auto %1 = new %2(creator, engine, %3);"_s.arg( + objectName, object->internalName(), qobjectParent); + current.init.body << u"creator->set(%1, %2);"_s.arg( + QString::number(m_visitor->creationIndex(object)), objectName); + + current.endInit.body << u"auto %1 = creator->get<%2>(%3);"_s.arg( + objectName, object->internalName(), + QString::number(m_visitor->creationIndex(object))); + } + + // NB: we expect one "on" assignment per property, so creating + // QQmlProperty each time should be fine (unlike QQmlListReference) + current.endInit.body << u"{"_s; + current.endInit.body << u"QQmlProperty qmlprop(%1, %2);"_s.arg( + accessor.name, QQmlJSUtils::toLiteral(propertyName)); + current.endInit.body << u"QT_PREPEND_NAMESPACE(QQmlCppOnAssignmentHelper)::set(%1, qmlprop);"_s + .arg(objectName); + current.endInit.body << u"}"_s; +} + +/*! + * \internal + * Helper method used to keep compileBindingByType() readable. + */ +void QmltcCompiler::compileAttachedPropertyBinding(QmltcType ¤t, + const QQmlJSMetaPropertyBinding &binding, + const QQmlJSScope::ConstPtr &type, + const BindingAccessorData &accessor) +{ + Q_ASSERT(binding.bindingType() == QQmlSA::BindingType::AttachedProperty); + + const QString &propertyName = binding.propertyName(); + const QQmlJSMetaProperty property = type->property(propertyName); + QQmlJSScope::ConstPtr propertyType = property.type(); + + Q_ASSERT(accessor.name == u"this"_s); // doesn't have to hold, in fact + const auto attachedType = binding.attachingType(); + Q_ASSERT(attachedType); + + const QString attachingTypeName = propertyName; // acts as an identifier + auto attachingType = m_typeResolver->typeForName(attachingTypeName); + + QString attachedTypeName = attachedType->baseTypeName(); + Q_ASSERT(!attachedTypeName.isEmpty()); + + auto &attachedMemberName = + m_uniques[UniqueStringId(current, propertyName)].attachedVariableName; + if (attachedMemberName.isEmpty()) { + attachedMemberName = u"m_" + attachingTypeName; + // add attached type as a member variable to allow noop lookup + current.variables.emplaceBack(attachedTypeName + u" *", attachedMemberName, u"nullptr"_s); + + if (propertyName == u"Component"_s) { // Component attached type is special + current.endInit.body << u"Q_ASSERT(qmlEngine(this));"_s; + current.endInit.body + << u"// attached Component must be added to the object's QQmlData"_s; + current.endInit.body + << u"Q_ASSERT(!QQmlEnginePrivate::get(qmlEngine(this))->activeObjectCreator);"_s; + } + + // Note: getting attached property is fairly expensive + const QString getAttachedPropertyLine = u"qobject_cast<" + attachedTypeName + + u" *>(qmlAttachedPropertiesObject<" + attachingType->internalName() + + u">(this, /* create = */ true))"; + current.endInit.body << attachedMemberName + u" = " + getAttachedPropertyLine + u";"; + + if (propertyName == u"Component"_s) { + // call completed/destruction signals appropriately + current.handleOnCompleted.body << u"Q_EMIT " + attachedMemberName + u"->completed();"; + if (!current.dtor) { + current.dtor = QmltcDtor{}; + current.dtor->name = u"~" + current.cppType; + } + current.dtor->body << u"Q_EMIT " + attachedMemberName + u"->destruction();"; + } + } + + auto subbindings = attachedType->ownPropertyBindingsInQmlIROrder(); + // compile bindings of the attached property + partitionBindings(subbindings.begin(), subbindings.end()); + compileBinding(current, subbindings.begin(), subbindings.end(), attachedType, + { type, attachedMemberName, propertyName, false }); +} + +/*! + * \internal + * Helper method used to keep compileBindingByType() readable. + */ +void QmltcCompiler::compileGroupPropertyBinding(QmltcType ¤t, + const QQmlJSMetaPropertyBinding &binding, + const QQmlJSScope::ConstPtr &type, + const BindingAccessorData &accessor) +{ + Q_ASSERT(binding.bindingType() == QQmlSA::BindingType::GroupProperty); + + const QString &propertyName = binding.propertyName(); + const QQmlJSMetaProperty property = type->property(propertyName); + QQmlJSScope::ConstPtr propertyType = property.type(); + + Q_ASSERT(accessor.name == u"this"_s); // doesn't have to hold, in fact + if (property.read().isEmpty()) { + recordError(binding.sourceLocation(), + u"READ function of group property '" + propertyName + u"' is unknown"); + return; + } + + auto groupType = binding.groupType(); + Q_ASSERT(groupType); + + const bool isValueType = propertyType->accessSemantics() == QQmlJSScope::AccessSemantics::Value; + if (!isValueType + && propertyType->accessSemantics() != QQmlJSScope::AccessSemantics::Reference) { + recordError(binding.sourceLocation(), + u"Group property '" + propertyName + u"' has unsupported access semantics"); + return; + } + + auto subbindings = groupType->ownPropertyBindingsInQmlIROrder(); + auto firstScript = partitionBindings(subbindings.begin(), subbindings.end()); + + // if we have no non-script bindings, we have no bindings that affect + // the value type group, so no reason to generate the wrapping code + const bool generateValueTypeCode = isValueType && (subbindings.begin() != firstScript); + + QString groupAccessor = QmltcCodeGenerator::wrap_privateClass(accessor.name, property) + u"->" + + property.read() + u"()"; + // NB: used when isValueType == true + const QString groupPropertyVarName = accessor.name + u"_group_" + propertyName; + // value types are special + if (generateValueTypeCode) { + if (property.write().isEmpty()) { // just reject this + recordError(binding.sourceLocation(), + u"Group property '" + propertyName + u"' is a value type without a setter"); + return; + } + + current.endInit.body << u"auto " + groupPropertyVarName + u" = " + groupAccessor + u";"; + // addressof operator is to make the binding logic work, which + // expects that `accessor.name` is a pointer type + groupAccessor = QmltcCodeGenerator::wrap_addressof(groupPropertyVarName); + } + + // compile bindings of the grouped property + const auto compile = [&](const auto &bStart, const auto &bEnd) { + compileBinding(current, bStart, bEnd, groupType, + { type, groupAccessor, propertyName, isValueType }); + }; + + auto it = subbindings.begin(); + Q_ASSERT(std::all_of(it, firstScript, [](const auto &x) { + return x.bindingType() != QQmlSA::BindingType::Script; + })); + compile(it, firstScript); + it = firstScript; + + // NB: script bindings are special on group properties. if our group is + // a value type, the binding would be installed on the *object* that + // holds the value type and not on the value type itself. this may cause + // subtle side issues (esp. when script binding is actually a simple + // enum value assignment - which is not recognized specially): + // + // auto valueTypeGroupProperty = getCopy(); + // installBinding(valueTypeGroupProperty, "subproperty1"); // changes subproperty1 value + // setCopy(valueTypeGroupProperty); // oops, subproperty1 value changed to old again + if (generateValueTypeCode) { // write the value type back + current.endInit.body << QmltcCodeGenerator::wrap_privateClass(accessor.name, property) + + u"->" + property.write() + u"(" + groupPropertyVarName + u");"; + } + + // once the value is written back, process the script bindings + Q_ASSERT(std::all_of(it, subbindings.end(), [](const auto &x) { + return x.bindingType() == QQmlSA::BindingType::Script; + })); + compile(it, subbindings.end()); +} + +/*! + * \internal + * Helper method used to keep compileBindingByType() readable. + */ +void QmltcCompiler::compileTranslationBinding(QmltcType ¤t, + const QQmlJSMetaPropertyBinding &binding, + const QQmlJSScope::ConstPtr &type, + const BindingAccessorData &accessor) +{ + Q_ASSERT(binding.bindingType() == QQmlSA::BindingType::Translation + || binding.bindingType() == QQmlSA::BindingType::TranslationById); + + const QString &propertyName = binding.propertyName(); + + auto [property, absoluteIndex] = getMetaPropertyIndex(type, propertyName); + + if (absoluteIndex < 0) { + recordError(binding.sourceLocation(), + u"Binding on unknown property '" + propertyName + u"'"); + return; + } + + QString bindingTarget = accessor.name; + + int valueTypeIndex = -1; + if (accessor.isValueType) { + Q_ASSERT(accessor.scope != type); + bindingTarget = u"this"_s; // TODO: not necessarily "this"? + auto [groupProperty, groupPropertyIndex] = + getMetaPropertyIndex(accessor.scope, accessor.propertyName); + if (groupPropertyIndex < 0) { + recordError(binding.sourceLocation(), + u"Binding on group property '" + accessor.propertyName + + u"' of unknown type"); + return; + } + valueTypeIndex = absoluteIndex; + absoluteIndex = groupPropertyIndex; // e.g. index of accessor.name + } + + QmltcCodeGenerator::TranslationBindingInfo info; + info.unitVarName = generate_callCompilationUnit(m_urlMethodName); + info.scope = u"this"_s; + info.target = u"this"_s; + info.propertyIndex = absoluteIndex; + info.property = property; + info.data = binding.translationDataValue(m_url); + info.valueTypeIndex = valueTypeIndex; + info.line = binding.sourceLocation().startLine; + info.column = binding.sourceLocation().startColumn; + + QmltcCodeGenerator::generate_createTranslationBindingOnProperty(¤t.endInit.body, info); +} + +void QmltcCompiler::processLastListBindings(QmltcType ¤t, const QQmlJSScope::ConstPtr &type, + const BindingAccessorData &accessor) +{ + if (unprocessedListBindings.empty()) + return; + + QmltcCodeGenerator::generate_assignToListProperty( + ¤t.endInit.body, type, unprocessedListProperty, unprocessedListBindings, + accessor.name, + m_uniques[UniqueStringId(current, unprocessedListProperty.propertyName())] + .qmlListVariableName); + + unprocessedListBindings.clear(); +} + +void QmltcCompiler::compileBinding(QmltcType ¤t, + QList<QQmlJSMetaPropertyBinding>::iterator bindingStart, + QList<QQmlJSMetaPropertyBinding>::iterator bindingEnd, + const QQmlJSScope::ConstPtr &type, + const BindingAccessorData &accessor) +{ + for (auto it = bindingStart; it != bindingEnd; it++) { + const QQmlJSMetaPropertyBinding &binding = *it; + const QString &propertyName = binding.propertyName(); + Q_ASSERT(!propertyName.isEmpty()); + + // Note: unlike QQmlObjectCreator, we don't have to do a complicated + // deferral logic for bindings: if a binding is deferred, it is not compiled + // (potentially, with all the bindings inside of it), period. + if (type->isNameDeferred(propertyName)) { + const auto location = binding.sourceLocation(); + // make sure group property is not generalized by checking if type really has a property + // called propertyName. If not, it is probably an id. + if (binding.bindingType() == QQmlSA::BindingType::GroupProperty + && type->hasProperty(propertyName)) { + qCWarning(lcQmltcCompiler) + << QStringLiteral("Binding at line %1 column %2 is not deferred as it is a " + "binding on a group property.") + .arg(QString::number(location.startLine), + QString::number(location.startColumn)); + // we do not support PropertyChanges and other types with similar + // behavior yet, so this binding is compiled + } else { + qCDebug(lcQmltcCompiler) + << QStringLiteral( + "Binding at line %1 column %2 is deferred and thus not compiled") + .arg(QString::number(location.startLine), + QString::number(location.startColumn)); + continue; + } + } + + const QQmlJSMetaProperty metaProperty = type->property(propertyName); + const QQmlJSScope::ConstPtr propertyType = metaProperty.type(); + + if (!(qIsReferenceTypeList(metaProperty) && unprocessedListProperty == metaProperty)) { + processLastListBindings(current, type, accessor); + } + + compileBindingByType(current, binding, type, accessor); + } + + processLastListBindings(current, type, accessor); +} + +void QmltcCompiler::compileBindingByType(QmltcType ¤t, + const QQmlJSMetaPropertyBinding &binding, + const QQmlJSScope::ConstPtr &type, + const BindingAccessorData &accessor) +{ + const QString &propertyName = binding.propertyName(); + const QQmlJSMetaProperty metaProperty = type->property(propertyName); + const QQmlJSScope::ConstPtr propertyType = metaProperty.type(); + + const auto assignToProperty = [&](const QQmlJSMetaProperty &p, const QString &value, + bool constructFromQObject = false) { + QmltcCodeGenerator::generate_assignToProperty(¤t.endInit.body, type, p, value, + accessor.name, constructFromQObject); + }; + switch (binding.bindingType()) { + case QQmlSA::BindingType::BoolLiteral: { + const bool value = binding.boolValue(); + assignToProperty(metaProperty, value ? u"true"_s : u"false"_s); + break; + } + case QQmlSA::BindingType::NumberLiteral: { + assignToProperty(metaProperty, QString::number(binding.numberValue())); + break; + } + case QQmlSA::BindingType::StringLiteral: { + QString value = QQmlJSUtils::toLiteral(binding.stringValue()); + if (auto type = metaProperty.type()) { + if (type->internalName() == u"QUrl"_s) { + value = u"QUrl(%1)"_s.arg(value); + } + } + assignToProperty(metaProperty, value); + break; + } + case QQmlSA::BindingType::RegExpLiteral: { + const QString value = + u"QRegularExpression(%1)"_s.arg(QQmlJSUtils::toLiteral(binding.regExpValue())); + assignToProperty(metaProperty, value); + break; + } + case QQmlSA::BindingType::Null: { + // poor check: null bindings are only supported for var and objects + Q_ASSERT(propertyType->isSameType(m_typeResolver->varType()) + || propertyType->accessSemantics() == QQmlJSScope::AccessSemantics::Reference); + if (propertyType->accessSemantics() == QQmlJSScope::AccessSemantics::Reference) + assignToProperty(metaProperty, u"nullptr"_s); + else + assignToProperty(metaProperty, u"QVariant::fromValue(nullptr)"_s); + break; + } + case QQmlSA::BindingType::Script: { + QString bindingSymbolName = type->internalName() + u'_' + propertyName + u"_binding"; + bindingSymbolName.replace(u'.', u'_'); // can happen with group properties + compileScriptBinding(current, binding, bindingSymbolName, type, propertyName, propertyType, + accessor); + break; + } + case QQmlSA::BindingType::Object: { + compileObjectBinding(current, binding, type, accessor); + break; + } + case QQmlSA::BindingType::Interceptor: + Q_FALLTHROUGH(); + case QQmlSA::BindingType::ValueSource: { + compileValueSourceOrInterceptorBinding(current, binding, type, accessor); + break; + } + case QQmlSA::BindingType::AttachedProperty: { + compileAttachedPropertyBinding(current, binding, type, accessor); + break; + } + case QQmlSA::BindingType::GroupProperty: { + compileGroupPropertyBinding(current, binding, type, accessor); + break; + } + + case QQmlSA::BindingType::TranslationById: + case QQmlSA::BindingType::Translation: { + compileTranslationBinding(current, binding, type, accessor); + break; + } + case QQmlSA::BindingType::Invalid: { + recordError(binding.sourceLocation(), u"This binding is invalid"_s); + break; + } + default: { + recordError(binding.sourceLocation(), u"Binding is not supported"_s); + break; + } + } +} + +// returns compiled script binding for "property changed" handler in a form of object type +static QmltcType compileScriptBindingPropertyChangeHandler(const QQmlJSMetaPropertyBinding &binding, + const QQmlJSScope::ConstPtr &objectType, + const QString &urlMethodName, + const QString &functorCppType, + const QString &objectCppType) +{ + QmltcType bindingFunctor {}; + bindingFunctor.cppType = functorCppType; + bindingFunctor.ignoreInit = true; + + // default member variable and ctor: + const QString pointerToObject = objectCppType + u" *"; + bindingFunctor.variables.emplaceBack( + QmltcVariable { pointerToObject, u"m_self"_s, u"nullptr"_s }); + bindingFunctor.baselineCtor.name = functorCppType; + bindingFunctor.baselineCtor.parameterList.emplaceBack( + QmltcVariable { pointerToObject, u"self"_s, QString() }); + bindingFunctor.baselineCtor.initializerList.emplaceBack(u"m_self(self)"_s); + + // call operator: + QmltcMethod callOperator {}; + callOperator.returnType = u"void"_s; + callOperator.name = u"operator()"_s; + callOperator.modifiers << u"const"_s; + QmltcCodeGenerator::generate_callExecuteRuntimeFunction( + &callOperator.body, urlMethodName + u"()", + objectType->ownRuntimeFunctionIndex(binding.scriptIndex()), u"m_self"_s, u"void"_s, {}); + + bindingFunctor.functions.emplaceBack(std::move(callOperator)); + + return bindingFunctor; +} + +// finds property for given scope and returns it together with the absolute +// property index in the property array of the corresponding QMetaObject +static std::pair<QQmlJSMetaProperty, int> getMetaPropertyIndex(const QQmlJSScope::ConstPtr &scope, + const QString &propertyName) +{ + auto owner = QQmlJSScope::ownerOfProperty(scope, propertyName).scope; + Q_ASSERT(owner); + const QQmlJSMetaProperty p = owner->ownProperty(propertyName); + if (!p.isValid()) + return { p, -1 }; + int index = p.index(); + if (index < 0) // this property doesn't have index - comes from QML + return { p, -1 }; + + const auto increment = [&](const QQmlJSScope::ConstPtr &type, QQmlJSScope::ExtensionKind m) { + // owner of property is not included in the offset calculation (relative + // index is already added as p.index()) + if (type->isSameType(owner)) + return; + + // extension namespace and JavaScript properties are ignored + if (m == QQmlJSScope::ExtensionNamespace || m == QQmlJSScope::ExtensionJavaScript) + return; + + index += int(type->ownProperties().size()); + }; + QQmlJSUtils::traverseFollowingMetaObjectHierarchy(scope, owner, increment); + return { p, index }; +} + +void QmltcCompiler::compileScriptBinding(QmltcType ¤t, + const QQmlJSMetaPropertyBinding &binding, + const QString &bindingSymbolName, + const QQmlJSScope::ConstPtr &objectType, + const QString &propertyName, + const QQmlJSScope::ConstPtr &propertyType, + const QmltcCompiler::BindingAccessorData &accessor) +{ + const auto compileScriptSignal = [&](const QString &name) { + QString This_signal = u"this"_s; + QString This_slot = u"this"_s; + QString objectClassName_signal = objectType->internalName(); + QString objectClassName_slot = objectType->internalName(); + + // TODO: ugly crutch to make stuff work + if (accessor.name != u"this"_s) { // e.g. if attached property + This_signal = accessor.name; + This_slot = u"this"_s; // still + objectClassName_signal = objectType->baseTypeName(); + objectClassName_slot = current.cppType; // real base class where slot would go + } + Q_ASSERT(!objectClassName_signal.isEmpty()); + Q_ASSERT(!objectClassName_slot.isEmpty()); + + const auto signalMethods = objectType->methods(name, QQmlJSMetaMethodType::Signal); + Q_ASSERT(!signalMethods.isEmpty()); // an error somewhere else + QQmlJSMetaMethod signal = signalMethods.at(0); + Q_ASSERT(signal.methodType() == QQmlJSMetaMethodType::Signal); + + const QString signalName = signal.methodName(); + const QString slotName = newSymbol(signalName + u"_slot"); + + const QString signalReturnType = figureReturnType(signal); + const QList<QmltcVariable> slotParameters = + compileMethodParameters(signal.parameters(), /* allow unnamed = */ true); + + // SignalHander specific: + QmltcMethod slotMethod {}; + slotMethod.returnType = signalReturnType; + slotMethod.name = slotName; + slotMethod.parameterList = slotParameters; + + QmltcCodeGenerator::generate_callExecuteRuntimeFunction( + &slotMethod.body, m_urlMethodName + u"()", + objectType->ownRuntimeFunctionIndex(binding.scriptIndex()), + u"this"_s, // Note: because script bindings always use current QML object scope + signalReturnType, slotParameters); + slotMethod.type = QQmlJSMetaMethodType::Slot; + + current.functions << std::move(slotMethod); + current.setComplexBindings.body << u"QObject::connect(" + This_signal + u", " + u"&" + + objectClassName_signal + u"::" + signalName + u", " + This_slot + u", &" + + objectClassName_slot + u"::" + slotName + u");"; + }; + + switch (binding.scriptKind()) { + case QQmlSA::ScriptBindingKind::PropertyBinding: { + if (!propertyType) { + recordError(binding.sourceLocation(), + u"Binding on property '" + propertyName + u"' of unknown type"); + return; + } + + auto [property, absoluteIndex] = getMetaPropertyIndex(objectType, propertyName); + if (absoluteIndex < 0) { + recordError(binding.sourceLocation(), + u"Binding on unknown property '" + propertyName + u"'"); + return; + } + + QString bindingTarget = accessor.name; + + int valueTypeIndex = -1; + if (accessor.isValueType) { + Q_ASSERT(accessor.scope != objectType); + bindingTarget = u"this"_s; // TODO: not necessarily "this"? + auto [groupProperty, groupPropertyIndex] = + getMetaPropertyIndex(accessor.scope, accessor.propertyName); + if (groupPropertyIndex < 0) { + recordError(binding.sourceLocation(), + u"Binding on group property '" + accessor.propertyName + + u"' of unknown type"); + return; + } + valueTypeIndex = absoluteIndex; + absoluteIndex = groupPropertyIndex; // e.g. index of accessor.name + } + + QmltcCodeGenerator::generate_createBindingOnProperty( + ¤t.setComplexBindings.body, generate_callCompilationUnit(m_urlMethodName), + u"this"_s, // NB: always using enclosing object as a scope for the binding + static_cast<qsizetype>(objectType->ownRuntimeFunctionIndex(binding.scriptIndex())), + bindingTarget, // binding target + // value types are special and are bound through valueTypeIndex + accessor.isValueType ? QQmlJSScope::ConstPtr() : objectType, absoluteIndex, + property, valueTypeIndex, accessor.name); + break; + } + case QQmlSA::ScriptBindingKind::SignalHandler: { + const auto name = QQmlSignalNames::handlerNameToSignalName(propertyName); + Q_ASSERT(name.has_value()); + compileScriptSignal(*name); + break; + } + case QQmlSA ::ScriptBindingKind::ChangeHandler: { + const QString objectClassName = objectType->internalName(); + const QString bindingFunctorName = newSymbol(bindingSymbolName + u"Functor"); + + const auto signalName = QQmlSignalNames::handlerNameToSignalName(propertyName); + Q_ASSERT(signalName.has_value()); // an error somewhere else + const auto actualProperty = + QQmlJSUtils::propertyFromChangedHandler(objectType, propertyName); + Q_ASSERT(actualProperty.has_value()); // an error somewhere else + const auto actualPropertyType = actualProperty->type(); + if (!actualPropertyType) { + recordError(binding.sourceLocation(), + u"Binding on property '" + actualProperty->propertyName() + + u"' of unknown type"); + return; + } + + // due to historical reasons (QQmlObjectCreator), prefer NOTIFY over + // BINDABLE when both are available. thus, test for notify first + const QString notifyString = actualProperty->notify(); + if (!notifyString.isEmpty()) { + compileScriptSignal(notifyString); + break; + } + const QString bindableString = actualProperty->bindable(); + QString typeOfQmlBinding = + u"std::unique_ptr<QPropertyChangeHandler<" + bindingFunctorName + u">>"; + + current.children << compileScriptBindingPropertyChangeHandler( + binding, objectType, m_urlMethodName, bindingFunctorName, objectClassName); + + current.setComplexBindings.body << u"if (!%1.contains(\"%2\"))"_s.arg( + current.propertyInitializer.initializedCache.name, propertyName); + + // TODO: this could be dropped if QQmlEngine::setContextForObject() is + // done before currently generated C++ object is constructed + current.setComplexBindings.body << u" "_s + bindingSymbolName + u".reset(new QPropertyChangeHandler<" + + bindingFunctorName + u">(" + + QmltcCodeGenerator::wrap_privateClass(accessor.name, *actualProperty) + + u"->" + bindableString + u"().onValueChanged(" + bindingFunctorName + u"(" + + accessor.name + u"))));"; + + current.variables.emplaceBack( + QmltcVariable { typeOfQmlBinding, bindingSymbolName, QString() }); + break; + } + default: + recordError(binding.sourceLocation(), u"Invalid script binding found"_s); + break; + } +} + +QT_END_NAMESPACE diff --git a/tools/qmltc/qmltccompiler.h b/tools/qmltc/qmltccompiler.h new file mode 100644 index 0000000000..3deab6d44e --- /dev/null +++ b/tools/qmltc/qmltccompiler.h @@ -0,0 +1,206 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef QMLTCCOMPILER_H +#define QMLTCCOMPILER_H + +#include "qmltctyperesolver.h" +#include "qmltcvisitor.h" +#include "qmltcoutputir.h" + +#include <QtCore/qcommandlineparser.h> +#include <QtCore/qcoreapplication.h> +#include <QtCore/qstring.h> +#include <QtCore/qhash.h> + +#include <private/qqmljslogger_p.h> + +#include <memory> + +QT_BEGIN_NAMESPACE + +struct QmltcCompilerInfo +{ + QString outputCppFile; + QString outputHFile; + QString outputNamespace; + QString resourcePath; + QString exportMacro; + QString exportInclude; +}; + +class QmltcCompiler +{ + using InlineComponentOrDocumentRootName = QQmlJSScope::InlineComponentOrDocumentRootName; + using InlineComponentNameType = QQmlJSScope::InlineComponentNameType; + using RootDocumentNameType = QQmlJSScope::RootDocumentNameType; + +public: + QmltcCompiler(const QString &url, QmltcTypeResolver *resolver, QmltcVisitor *visitor, + QQmlJSLogger *logger); + void compile(const QmltcCompilerInfo &info); + + ~QmltcCompiler(); + + /*! \internal + + Returns \c true if \a binding is considered complex by the compiler + (requires special code generation) + */ + static bool isComplexBinding(const QQmlJSMetaPropertyBinding &binding) + { + // TODO: translation bindings (once supported) are also complex? + return binding.bindingType() == QQmlSA::BindingType::Script; + } + +private: + QString m_url; // QML input file url + QmltcTypeResolver *m_typeResolver = nullptr; + QmltcVisitor *m_visitor = nullptr; + QQmlJSLogger *m_logger = nullptr; + QmltcCompilerInfo m_info {}; // miscellaneous input/output information + QString m_urlMethodName; + + struct UniqueStringId; + struct QmltcTypeLocalData; + // per-type, per-property code generation cache of created symbols + QHash<UniqueStringId, QmltcTypeLocalData> m_uniques; + + void compileUrlMethod(QmltcMethod &urlMethod, const QString &urlMethodName); + void + compileType(QmltcType ¤t, const QQmlJSScope::ConstPtr &type, + std::function<void(QmltcType &, const QQmlJSScope::ConstPtr &)> compileElements); + void compileTypeElements(QmltcType ¤t, const QQmlJSScope::ConstPtr &type); + void compileEnum(QmltcType ¤t, const QQmlJSMetaEnum &e); + void compileMethod(QmltcType ¤t, const QQmlJSMetaMethod &m, + const QQmlJSScope::ConstPtr &owner); + void compileProperty(QmltcType ¤t, const QQmlJSMetaProperty &p, + const QQmlJSScope::ConstPtr &owner); + void compileAlias(QmltcType ¤t, const QQmlJSMetaProperty &alias, + const QQmlJSScope::ConstPtr &owner); + void compileExtraListMethods(QmltcType ¤t, const QQmlJSMetaProperty &p); + + /*! + \internal + + Helper structure that holds the information necessary for most bindings, + such as accessor name, which is used to reference the properties. For + example: + > (accessor.name)->(propertyName) results in "this->myProperty" + + This data is also used in more advanced scenarios by attached and + grouped properties + */ + struct BindingAccessorData + { + QQmlJSScope::ConstPtr scope; // usually the current type + QString name = QStringLiteral("this"); + QString propertyName = QString(); + bool isValueType = false; + }; + + QStringList unprocessedListBindings; + QQmlJSMetaProperty unprocessedListProperty; + + void processLastListBindings(QmltcType ¤t, const QQmlJSScope::ConstPtr &type, + const BindingAccessorData &accessor); + + void compileBinding(QmltcType ¤t, QList<QQmlJSMetaPropertyBinding>::iterator bindingStart, + QList<QQmlJSMetaPropertyBinding>::iterator bindingEnd, + const QQmlJSScope::ConstPtr &type, const BindingAccessorData &accessor); + + void compileBindingByType(QmltcType ¤t, const QQmlJSMetaPropertyBinding &binding, + const QQmlJSScope::ConstPtr &type, + const BindingAccessorData &accessor); + + void compileObjectBinding(QmltcType ¤t, const QQmlJSMetaPropertyBinding &binding, + const QQmlJSScope::ConstPtr &type, + const BindingAccessorData &accessor); + + void compileValueSourceOrInterceptorBinding(QmltcType ¤t, + const QQmlJSMetaPropertyBinding &binding, + const QQmlJSScope::ConstPtr &type, + const BindingAccessorData &accessor); + + void compileAttachedPropertyBinding(QmltcType ¤t, + const QQmlJSMetaPropertyBinding &binding, + const QQmlJSScope::ConstPtr &type, + const BindingAccessorData &accessor); + + void compileGroupPropertyBinding(QmltcType ¤t, const QQmlJSMetaPropertyBinding &binding, + const QQmlJSScope::ConstPtr &type, + const BindingAccessorData &accessor); + + void compileTranslationBinding(QmltcType ¤t, const QQmlJSMetaPropertyBinding &binding, + const QQmlJSScope::ConstPtr &type, + const BindingAccessorData &accessor); + + // special case (for simplicity) + void compileScriptBinding(QmltcType ¤t, const QQmlJSMetaPropertyBinding &binding, + const QString &bindingSymbolName, const QQmlJSScope::ConstPtr &type, + const QString &propertyName, + const QQmlJSScope::ConstPtr &propertyType, + const BindingAccessorData &accessor); + + /*! + \internal + Helper structure that acts as a key in a hash-table of + QmltcType-specific data (such as local variable names). Using a + hash-table allows to avoid creating the same variables multiple times + during binding compilation, which leads to better code generation and + faster object creation. This is really something that the QML optimizer + should do, but we have only this home-grown alternative at the moment + */ + struct UniqueStringId + { + QString unique; + UniqueStringId(const QmltcType &context, const QString &property) + : unique(context.cppType + u"_" + property) // this is unique enough + { + Q_ASSERT(!context.cppType.isEmpty()); + Q_ASSERT(!property.isEmpty()); + } + friend bool operator==(const UniqueStringId &x, const UniqueStringId &y) + { + return x.unique == y.unique; + } + friend bool operator!=(const UniqueStringId &x, const UniqueStringId &y) + { + return !(x == y); + } + friend size_t qHash(const UniqueStringId &x, size_t seed = 0) + { + return qHash(x.unique, seed); + } + }; + + struct QmltcTypeLocalData + { + // empty QString() means that the local data is not present (yet) + QString qmlListVariableName; + QString onAssignmentObjectName; + QString attachedVariableName; + }; + + QHash<QString, qsizetype> m_symbols; + QString newSymbol(const QString &base); + + bool hasErrors() const { return m_logger->hasErrors(); } + void recordError(const QQmlJS::SourceLocation &location, const QString &message, + QQmlJS::LoggerWarningId id = qmlCompiler) + { + // pretty much any compiler error is a critical error (we cannot + // generate code - compilation fails) + m_logger->log(message, id, location); + } + void recordError(const QV4::CompiledData::Location &location, const QString &message, + QQmlJS::LoggerWarningId id = qmlCompiler) + { + recordError(QQmlJS::SourceLocation { 0, 0, location.line(), location.column() }, message, + id); + } +}; + +QT_END_NAMESPACE + +#endif // QMLTCCOMPILER_H diff --git a/tools/qmltc/qmltccompilerpieces.cpp b/tools/qmltc/qmltccompilerpieces.cpp new file mode 100644 index 0000000000..cd1735bc07 --- /dev/null +++ b/tools/qmltc/qmltccompilerpieces.cpp @@ -0,0 +1,352 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qmltccompilerpieces.h" + +#include <private/qqmljsutils_p.h> + +#include <tuple> + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +static QString scopeName(const QQmlJSScope::ConstPtr &scope) +{ + Q_ASSERT(scope->isFullyResolved()); + const auto scopeType = scope->scopeType(); + if (scopeType == QQmlSA::ScopeType::GroupedPropertyScope + || scopeType == QQmlSA::ScopeType::AttachedPropertyScope) { + return scope->baseType()->internalName(); + } + return scope->internalName(); +} + +QmltcCodeGenerator::PreparedValue +QmltcCodeGenerator::wrap_extensionType(const QQmlJSScope::ConstPtr &type, + const QQmlJSMetaProperty &p, const QString &accessor) +{ + Q_ASSERT(type->isFullyResolved()); + + QStringList prologue; + QString value = accessor; + QStringList epilogue; + + auto [owner, ownerKind] = QQmlJSScope::ownerOfProperty(type, p.propertyName()); + Q_ASSERT(owner); + Q_ASSERT(owner->isFullyResolved()); + + // properties are only visible when we use QML_{NAMESPACE_}EXTENDED + if (ownerKind == QQmlJSScope::ExtensionType) { + // extensions is a C++-only feature: + Q_ASSERT(!owner->isComposite()); + + // have to wrap the property into an extension, but we need to figure + // out whether the type is QObject-based or not + prologue << u"{"_s; + const QString extensionObjectName = u"extObject"_s; + if (type->accessSemantics() == QQmlJSScope::AccessSemantics::Reference) { + // we have a Q_OBJECT. in this case, we call qmlExtendedObject() + // function that should return us the extension object. for that we + // have to figure out which specific extension we want here + + int extensionIndex = 0; + auto cppBase = QQmlJSScope::nonCompositeBaseType(type); + for (auto t = cppBase; t; t = t->baseType()) { + if (auto [ext, kind] = t->extensionType(); kind != QQmlJSScope::NotExtension) { + if (ext->isSameType(owner)) + break; + ++extensionIndex; + } + } + + prologue << u"static_assert(std::is_base_of<%1, %2>::value);"_s.arg(u"QObject"_s, + scopeName(type)); + prologue << u"auto %1 = qobject_cast<%2 *>(QQmlPrivate::qmlExtendedObject(%3, %4));"_s + .arg(extensionObjectName, owner->internalName(), accessor, + QString::number(extensionIndex)); + } else { + // we have a Q_GADGET. the assumption for extension types is that we + // can reinterpret_cast a Q_GADGET object into an extension type + // object and then interact with the extension object right away + prologue << u"static_assert(sizeof(%1) == sizeof(%2));"_s.arg(scopeName(type), + owner->internalName()); + prologue << u"static_assert(alignof(%1) == alignof(%2));"_s.arg(scopeName(type), + owner->internalName()); + prologue << u"auto %1 = reinterpret_cast<%2 *>(%3);"_s.arg( + extensionObjectName, owner->internalName(), accessor); + } + prologue << u"Q_ASSERT(%1);"_s.arg(extensionObjectName); + value = extensionObjectName; + epilogue << u"}"_s; + } + + return { prologue, value, epilogue }; +} + +void QmltcCodeGenerator::generate_assignToListProperty( + QStringList *block, const QQmlJSScope::ConstPtr &type, const QQmlJSMetaProperty &p, + const QStringList &values, const QString &accessor, QString &qmlListVarName) +{ + Q_UNUSED(type); // might be needed + const bool populateLocalListProperty = qmlListVarName.isEmpty(); + + if (populateLocalListProperty) { + auto [extensionPrologue, extensionAccessor, extensionEpilogue] = + QmltcCodeGenerator::wrap_extensionType( + type, p, QmltcCodeGenerator::wrap_privateClass(accessor, p)); + + qmlListVarName = u"listprop_%1"_s.arg(p.propertyName()); + QQmlJSScope::ConstPtr valueType = p.type()->valueType(); + *block << u"QQmlListProperty<%1> %2;"_s.arg(valueType->internalName(), qmlListVarName); + *block << extensionPrologue; + *block << u"%1 = %2->%3();"_s.arg(qmlListVarName, extensionAccessor, p.read()); + *block << extensionEpilogue; + } + for (const QString &value : values) { + auto [prologue, wrappedValue, epilogue] = + QmltcCodeGenerator::wrap_mismatchingTypeConversion(p, value); + *block << prologue; + *block << u"%1.append(std::addressof(%1), %2);"_s.arg(qmlListVarName, wrappedValue); + *block << epilogue; + } +} + +void QmltcCodeGenerator::generate_assignToProperty(QStringList *block, + const QQmlJSScope::ConstPtr &type, + const QQmlJSMetaProperty &p, + const QString &value, const QString &accessor, + bool constructFromQObject) +{ + Q_ASSERT(block); + Q_ASSERT(p.isValid()); + Q_ASSERT(!p.isList()); // NB: this code does not handle list properties + + const QString propertyName = p.propertyName(); + + if (type->hasOwnProperty(p.propertyName()) && !p.isAlias()) { + Q_ASSERT(!p.isPrivate()); + // this object is compiled, so just assignment should work fine + auto [prologue, wrappedValue, epilogue] = + QmltcCodeGenerator::wrap_mismatchingTypeConversion(p, value); + *block += prologue; + *block << u"%1->m_%2 = %3;"_s.arg(accessor, propertyName, wrappedValue); + *block += epilogue; + } else if (QString propertySetter = p.write(); !propertySetter.isEmpty() + && !QQmlJSUtils::bindablePropertyHasDefaultAccessor( + p, QQmlJSUtils::PropertyAccessor_Write)) { + // there's a WRITE function + auto [prologue, wrappedValue, epilogue] = + QmltcCodeGenerator::wrap_mismatchingTypeConversion(p, value); + *block += prologue; + + auto [extensionPrologue, extensionAccessor, extensionEpilogue] = + QmltcCodeGenerator::wrap_extensionType( + type, p, QmltcCodeGenerator::wrap_privateClass(accessor, p)); + *block += extensionPrologue; + *block << extensionAccessor + u"->" + propertySetter + u"(" + wrappedValue + u");"; + *block += extensionEpilogue; + + *block += epilogue; + } else { + // this property is weird, fallback to `setProperty` + *block << u"{ // couldn't find property setter, so using QObject::setProperty()"_s; + QString val = value; + if (constructFromQObject) { + const QString variantName = u"var_" + propertyName; + *block << u"QVariant " + variantName + u";"; + *block << variantName + u".setValue(" + val + u");"; + val = u"std::move(" + variantName + u")"; + } + // NB: setProperty() would handle private properties + *block << accessor + u"->setProperty(\"" + propertyName + u"\", " + val + u");"; + *block << u"}"_s; + } +} + +void QmltcCodeGenerator::generate_setIdValue(QStringList *block, const QString &context, + qsizetype index, const QString &accessor, + const QString &idString) +{ + Q_ASSERT(index >= 0); + *block << u"Q_ASSERT(%1 < %2->numIdValues()); // make sure Id is in bounds"_s.arg(index).arg( + context); + *block << u"%1->setIdValue(%2 /* id: %3 */, %4);"_s.arg(context, QString::number(index), + idString, accessor); +} + +void QmltcCodeGenerator::generate_callExecuteRuntimeFunction( + QStringList *block, const QString &url, QQmlJSMetaMethod::AbsoluteFunctionIndex index, + const QString &accessor, const QString &returnType, const QList<QmltcVariable> ¶meters) +{ + *block << u"QQmlEnginePrivate *e = QQmlEnginePrivate::get(qmlEngine(" + accessor + u"));"; + + const QString returnValueName = u"_ret"_s; + QStringList args; + args.reserve(parameters.size() + 1); + QStringList types; + types.reserve(parameters.size() + 1); + if (returnType == u"void"_s) { + args << u"nullptr"_s; + types << u"QMetaType::fromType<void>()"_s; + } else { + *block << returnType + u" " + returnValueName + u"{};"; // TYPE _ret{}; + args << u"const_cast<void *>(reinterpret_cast<const void *>(std::addressof(" + + returnValueName + u")))"; + types << u"QMetaType::fromType<std::decay_t<" + returnType + u">>()"; + } + + for (const QmltcVariable &p : parameters) { + args << u"const_cast<void *>(reinterpret_cast<const void *>(std::addressof(" + p.name + + u")))"; + types << u"QMetaType::fromType<std::decay_t<" + p.cppType + u">>()"; + } + + *block << u"void *_a[] = { " + args.join(u", "_s) + u" };"; + *block << u"QMetaType _t[] = { " + types.join(u", "_s) + u" };"; + const qsizetype runtimeIndex = static_cast<qsizetype>(index); + Q_ASSERT(runtimeIndex >= 0); + *block << u"e->executeRuntimeFunction(" + url + u", " + QString::number(runtimeIndex) + u", " + + accessor + u", " + QString::number(parameters.size()) + u", _a, _t);"; + if (returnType != u"void"_s) + *block << u"return " + returnValueName + u";"; +} + +void QmltcCodeGenerator::generate_createBindingOnProperty( + QStringList *block, const QString &unitVarName, const QString &scope, + qsizetype functionIndex, const QString &target, const QQmlJSScope::ConstPtr &targetType, + int propertyIndex, const QQmlJSMetaProperty &p, int valueTypeIndex, + const QString &subTarget) +{ + const QString propName = QQmlJSUtils::toLiteral(p.propertyName()); + if (QString bindable = p.bindable(); !bindable.isEmpty()) { + // TODO: test that private properties are bindable + QString createBindingForBindable = u"QT_PREPEND_NAMESPACE(QQmlCppBinding)::" + u"createBindingForBindable(" + + unitVarName + u", " + scope + u", " + QString::number(functionIndex) + u", " + + target + u", " + QString::number(propertyIndex) + u", " + + QString::number(valueTypeIndex) + u", " + propName + u")"; + const QString accessor = (valueTypeIndex == -1) ? target : subTarget; + + QStringList prologue; + QString value = QmltcCodeGenerator::wrap_privateClass(accessor, p); + QStringList epilogue; + if (targetType) { + auto [pro, v, epi] = QmltcCodeGenerator::wrap_extensionType(targetType, p, value); + std::tie(prologue, value, epilogue) = std::make_tuple(pro, v, epi); + } + + *block += prologue; + *block << u"if (!initializedCache.contains(\"%1\"))"_s.arg(p.propertyName()); + *block << u" "_s + value + u"->" + bindable + u"().setBinding(" + createBindingForBindable + u");"; + *block += epilogue; + } else { + QString createBindingForNonBindable = + u" "_s + + u"QT_PREPEND_NAMESPACE(QQmlCppBinding)::createBindingForNonBindable(" + unitVarName + + u", " + scope + u", " + QString::number(functionIndex) + u", " + target + u", " + + QString::number(propertyIndex) + u", " + QString::number(valueTypeIndex) + u", " + + propName + u")"; + // Note: in this version, the binding is set implicitly + *block << u"if (!initializedCache.contains(\"%1\"))"_s.arg(p.propertyName()); + *block << createBindingForNonBindable + u";"; + } +} + +void QmltcCodeGenerator::generate_createTranslationBindingOnProperty( + QStringList *block, const TranslationBindingInfo &info) +{ + const QString propName = QQmlJSUtils::toLiteral(info.property.propertyName()); + const QString qqmlTranslation = info.data.serializeForQmltc(); + + if (QString bindable = info.property.bindable(); !bindable.isEmpty()) { + // TODO: test that private properties are bindable + QString createTranslationCode = uR"(QT_PREPEND_NAMESPACE(QQmlCppBinding) + ::createTranslationBindingForBindable(%1, %2, %3, %4, %5))"_s + .arg(info.unitVarName, info.target) + .arg(info.propertyIndex) + .arg(qqmlTranslation, propName); + + *block << QmltcCodeGenerator::wrap_privateClass(info.target, info.property) + u"->" + + bindable + u"().setBinding(" + createTranslationCode + u");"; + } else { + QString locationString = + u"QQmlSourceLocation(%1->fileName(), %2, %3)"_s.arg(info.unitVarName) + .arg(info.line) + .arg(info.column); + QString createTranslationCode = uR"(QT_PREPEND_NAMESPACE(QQmlCppBinding) + ::createTranslationBindingForNonBindable( + %1, //unit + %2, //location + %3, //translationData + %4, //thisObject + %5, //bindingTarget + %6, //metaPropertyIndex + %7, //propertyName + %8) //valueTypePropertyIndex + )"_s.arg(info.unitVarName, locationString, qqmlTranslation, info.scope, info.target) + .arg(info.propertyIndex) + .arg(propName) + .arg(info.valueTypeIndex); + // Note: in this version, the binding is set implicitly + *block << createTranslationCode + u";"; + } +} + +QmltcCodeGenerator::PreparedValue +QmltcCodeGenerator::wrap_mismatchingTypeConversion(const QQmlJSMetaProperty &p, QString value) +{ + auto isDerivedFromBuiltin = [](const QQmlJSScope::ConstPtr &derived, const QString &builtin) { + for (QQmlJSScope::ConstPtr t = derived; t; t = t->baseType()) { + if (t->internalName() == builtin) + return true; + } + return false; + }; + QStringList prologue; + QStringList epilogue; + const QQmlJSScope::ConstPtr propType = p.type(); + if (isDerivedFromBuiltin(propType, u"QVariant"_s)) { + const QString variantName = u"var_" + p.propertyName(); + prologue << u"{ // accepts QVariant"_s; + prologue << u"QVariant " + variantName + u";"; + prologue << variantName + u".setValue(" + value + u");"; + epilogue << u"}"_s; + value = u"std::move(" + variantName + u")"; + } else if (isDerivedFromBuiltin(propType, u"QJSValue"_s)) { + const QString jsvalueName = u"jsvalue_" + p.propertyName(); + prologue << u"{ // accepts QJSValue"_s; + // Note: do not assume we have the engine, acquire it from `this` + prologue << u"auto e = qmlEngine(this);"_s; + prologue << u"QJSValue " + jsvalueName + u" = e->toScriptValue(" + value + u");"; + epilogue << u"}"_s; + value = u"std::move(" + jsvalueName + u")"; + } + return { prologue, value, epilogue }; +} + +QString QmltcCodeGenerator::wrap_privateClass(const QString &accessor, const QQmlJSMetaProperty &p) +{ + if (!p.isPrivate()) + return accessor; + + const QString privateType = p.privateClass(); + return u"static_cast<" + privateType + u" *>(QObjectPrivate::get(" + accessor + u"))"; +} + +QString QmltcCodeGenerator::wrap_qOverload(const QList<QmltcVariable> ¶meters, + const QString &overloaded) +{ + QStringList types; + types.reserve(parameters.size()); + for (const QmltcVariable &p : parameters) + types.emplaceBack(p.cppType); + return u"qOverload<" + types.join(u", "_s) + u">(" + overloaded + u")"; +} + +QString QmltcCodeGenerator::wrap_addressof(const QString &addressed) +{ + return u"std::addressof(" + addressed + u")"; +} + +QT_END_NAMESPACE diff --git a/tools/qmltc/qmltccompilerpieces.h b/tools/qmltc/qmltccompilerpieces.h new file mode 100644 index 0000000000..3252f19e86 --- /dev/null +++ b/tools/qmltc/qmltccompilerpieces.h @@ -0,0 +1,714 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef QMLTCCOMPILERPIECES_H +#define QMLTCCOMPILERPIECES_H + +#include <QtCore/qscopeguard.h> +#include <QtCore/qstringbuilder.h> +#include <QtCore/qfileinfo.h> + +#include <private/qqmljsutils_p.h> +#include <private/qqmlglobal_p.h> +#include <private/qqmltranslation_p.h> + +#include "qmltcoutputir.h" +#include "qmltcvisitor.h" + +QT_BEGIN_NAMESPACE + +/*! + \internal + + Helper class that generates code for the output IR. Takes care of + complicated, repetitive, nasty logic which is better kept in a single + confined place. +*/ +struct QmltcCodeGenerator +{ + static const QString privateEngineName; + static const QString typeCountName; + + QString documentUrl; + QmltcVisitor *visitor = nullptr; + + using InlineComponentOrDocumentRootName = QQmlJSScope::InlineComponentOrDocumentRootName; + using RootDocumentNameType = QQmlJSScope::RootDocumentNameType; + + [[nodiscard]] inline decltype(auto) generate_initCode(QmltcType ¤t, + const QQmlJSScope::ConstPtr &type) const; + inline void generate_initCodeForTopLevelComponent(QmltcType ¤t, + const QQmlJSScope::ConstPtr &type); + + inline void generate_qmltcInstructionCallCode(QmltcMethod *function, + const QQmlJSScope::ConstPtr &type, + const QString &baseInstructionArgs, + const QString &childInstructionArgs) const; + inline void generate_endInitCode(QmltcType ¤t, const QQmlJSScope::ConstPtr &type) const; + inline void generate_setComplexBindingsCode(QmltcType ¤t, + const QQmlJSScope::ConstPtr &type) const; + + inline void generate_interfaceCallCode(QmltcMethod *function, const QQmlJSScope::ConstPtr &type, + const QString &interfaceName, + const QString &interfaceCall) const; + inline void generate_beginClassCode(QmltcType ¤t, + const QQmlJSScope::ConstPtr &type) const; + inline void generate_completeComponentCode(QmltcType ¤t, + const QQmlJSScope::ConstPtr &type) const; + inline void generate_finalizeComponentCode(QmltcType ¤t, + const QQmlJSScope::ConstPtr &type) const; + inline void generate_handleOnCompletedCode(QmltcType ¤t, + const QQmlJSScope::ConstPtr &type) const; + + static void generate_assignToProperty(QStringList *block, const QQmlJSScope::ConstPtr &type, + const QQmlJSMetaProperty &p, const QString &value, + const QString &accessor, + bool constructFromQObject = false); + + static void generate_assignToListProperty(QStringList *block, const QQmlJSScope::ConstPtr &type, + const QQmlJSMetaProperty &p, const QStringList &value, + const QString &accessor, QString &qmlListVarName); + + static void generate_setIdValue(QStringList *block, const QString &context, qsizetype index, + const QString &accessor, const QString &idString); + + inline QString + generate_typeCount(const InlineComponentOrDocumentRootName &inlinedComponent) const + { + return generate_typeCount([](const QQmlJSScope::ConstPtr &) { return false; }, + inlinedComponent); + } + + /*! + * \internal + * Generate the constexpr typeCount expression for given inlinedComponent. Leave + * inlinedComponent empty to generate the expression for the main component. + */ + template<typename Predicate> + inline QString + generate_typeCount(Predicate p, + const InlineComponentOrDocumentRootName &inlinedComponent) const; + + static void generate_callExecuteRuntimeFunction(QStringList *block, const QString &url, + QQmlJSMetaMethod::AbsoluteFunctionIndex index, + const QString &accessor, + const QString &returnType, + const QList<QmltcVariable> ¶meters = {}); + + static void generate_createBindingOnProperty(QStringList *block, const QString &unitVarName, + const QString &scope, qsizetype functionIndex, + const QString &target, + const QQmlJSScope::ConstPtr &targetType, + int propertyIndex, const QQmlJSMetaProperty &p, + int valueTypeIndex, const QString &subTarget); + + // Used in generate_createTranslationBindingOnProperty to transport its numerous arguments. + struct TranslationBindingInfo + { + QString unitVarName; + QString scope; + QString target; + int propertyIndex; + QQmlJSMetaProperty property; + + QQmlTranslation data; + + int valueTypeIndex; + // For the source location of the translation binding + uint line; + // For the source location of the translation binding + uint column; + }; + + static void generate_createTranslationBindingOnProperty(QStringList *block, + const TranslationBindingInfo &info); + + static inline void generate_getCompilationUnitFromUrl(); + + struct PreparedValue + { + QStringList prologue; + QString value; + QStringList epilogue; + }; + + static PreparedValue wrap_mismatchingTypeConversion(const QQmlJSMetaProperty &p, QString value); + static PreparedValue wrap_extensionType(const QQmlJSScope::ConstPtr &type, + const QQmlJSMetaProperty &p, const QString &accessor); + + static QString wrap_privateClass(const QString &accessor, const QQmlJSMetaProperty &p); + static QString wrap_qOverload(const QList<QmltcVariable> ¶meters, + const QString &overloaded); + static QString wrap_addressof(const QString &addressed); + + QString urlMethodName() const + { + using namespace Qt::StringLiterals; + QFileInfo fi(documentUrl); + return u"q_qmltc_docUrl_" + fi.fileName().replace(u".qml"_s, u""_s).replace(u'.', u'_'); + } +}; + +/*! + \internal + + Generates \a{current.init}'s code. The init method sets up a + QQmlContext for the object and (in case \a type is a document + root) calls other object creation methods, and a user-provided + initialization callback, in a well-defined order: + 1. current.beginClass + 2. current.endInit + 3. user-provided initialization function + 4. current.setComplexBindings + 5. current.completeComponent + 6. current.finalizeComponent + 7. current.handleOnCompleted + + This function returns a QScopeGuard with the final instructions that have to + be generated at a later point, once everything else is compiled. + + \sa generate_initCodeForTopLevelComponent +*/ +inline decltype(auto) QmltcCodeGenerator::generate_initCode(QmltcType ¤t, + const QQmlJSScope::ConstPtr &type) const +{ + using namespace Qt::StringLiterals; + + // qmltc_init()'s parameters: + // * QQmltcObjectCreationHelper* creator + // * QQmlEngine* engine + // * const QQmlRefPointer<QQmlContextData>& parentContext + // * bool canFinalize [optional, when document root] + const bool isDocumentRoot = type == visitor->result(); + const bool isInlineComponent = type->isInlineComponent(); + + current.init.body << u"Q_UNUSED(creator)"_s; // can happen sometimes + + current.init.body << u"auto context = parentContext;"_s; + + // if parent scope has a QML base type and is not a (current) document root, + // the parentContext we passed as input to this object is a context of + // another document. we need to fix it by using parentContext->parent() + + const auto realQmlScope = [](const QQmlJSScope::ConstPtr &scope) { + if (scope->isArrayScope()) + return scope->parentScope(); + return scope; + }; + + if (auto parentScope = realQmlScope(type->parentScope()); + parentScope != visitor->result() && QQmlJSUtils::hasCompositeBase(parentScope)) { + current.init.body << u"// NB: context->parent() is the context of this document"_s; + current.init.body << u"context = context->parent();"_s; + } + + // any object with QML base class has to call base's init method + if (auto base = type->baseType(); base->isComposite()) { + QString lhs; + // init creates new context. for document root, it's going to be a real + // parent context, so store it temporarily in `context` variable + if (isDocumentRoot || isInlineComponent) + lhs = u"context = "_s; + current.init.body << u"// 0. call base's init method"_s; + + const auto isCurrentType = [&](const QQmlJSScope::ConstPtr &qmlType) { + return qmlType == type; + }; + const QString creationOffset = + generate_typeCount(isCurrentType, type->enclosingInlineComponentName()); + + current.init.body << u"{"_s; + current.init.body << u"QQmltcObjectCreationHelper subCreator(creator, %1);"_s.arg( + creationOffset); + current.init.body + << QStringLiteral("%1%2::%3(&subCreator, engine, context, /* finalize */ false);") + .arg(lhs, base->internalName(), current.init.name); + current.init.body << u"}"_s; + } + + current.init.body + << QStringLiteral("auto %1 = QQmlEnginePrivate::get(engine);").arg(privateEngineName); + current.init.body << QStringLiteral("Q_UNUSED(%1)").arg(privateEngineName); // precaution + + // when generating root or inlineComponents, we need to create a new (document-level) context. + // otherwise, just use existing context as is + if (isDocumentRoot || isInlineComponent) { + current.init.body << u"// 1. create new QML context for this document"_s; + current.init.body + << QStringLiteral( + "context = %1->createInternalContext(%1->compilationUnitFromUrl(%2()), " + "context, %3, true);") + .arg(privateEngineName, urlMethodName()) + .arg(this->visitor->creationIndex(type)); + } else { + current.init.body << u"// 1. use current context as this object's context"_s; + current.init.body << u"// context = context;"_s; + } + + if (!type->baseType()->isComposite() || isDocumentRoot || isInlineComponent) { + current.init.body << u"// 2. set context for this object"_s; + current.init.body << QStringLiteral( + "%1->setInternalContext(this, context, QQmlContextData::%2);") + .arg(privateEngineName, + (isDocumentRoot ? u"DocumentRoot"_s + : u"OrdinaryObject"_s)); + if (isDocumentRoot || isInlineComponent) + current.init.body << u"context->setContextObject(this);"_s; + } + + // context is this document's context. we must remember it in each type + current.variables.emplaceBack(u"QQmlRefPointer<QQmlContextData>"_s, u"q_qmltc_thisContext"_s, + u"nullptr"_s); + current.init.body << u"%1::q_qmltc_thisContext = context;"_s.arg(type->internalName()); + + if (int id = visitor->runtimeId(type); id >= 0) { + current.init.body << u"// 3. set id since it is provided"_s; + QString idString = visitor->addressableScopes().id(type, type); + if (idString.isEmpty()) + idString = u"<unknown>"_s; + QmltcCodeGenerator::generate_setIdValue(¤t.init.body, u"context"_s, id, u"this"_s, + idString); + } + + // if type has an extension, create a dynamic meta object for it + bool hasExtension = false; + for (auto cppBase = QQmlJSScope::nonCompositeBaseType(type); cppBase; + cppBase = cppBase->baseType()) { + // QObject is special: we have a pseudo-extension on it due to builtins + if (cppBase->internalName() == u"QObject"_s) + break; + if (cppBase->extensionType().extensionSpecifier != QQmlJSScope::NotExtension) { + hasExtension = true; + break; + } + } + if (hasExtension) { + current.init.body << u"{"_s; + current.init.body << u"auto cppData = QmltcTypeData(this);"_s; + current.init.body << u"qmltcCreateDynamicMetaObject(this, cppData);"_s; + current.init.body << u"}"_s; + } + + const auto generateFinalLines = [¤t, isDocumentRoot, isInlineComponent]() { + if (isDocumentRoot || isInlineComponent) { + current.init.body << u"// 4. finish the document root creation"_s; + current.init.body << u"if (canFinalize) {"_s; + current.init.body << QStringLiteral(" %1(creator, /* finalize */ true);") + .arg(current.beginClass.name); + current.init.body << QStringLiteral(" %1(creator, engine);") + .arg(current.endInit.name); + + current.init.body << QStringLiteral(" {"); + current.init.body << QStringLiteral(" PropertyInitializer propertyInitializer(*this);"); + current.init.body << QStringLiteral(" initializer(propertyInitializer);"); + current.init.body << QStringLiteral(" %1(creator, engine, propertyInitializer.initializedCache);").arg(current.setComplexBindings.name); + current.init.body << QStringLiteral(" }"); + + + current.init.body << QStringLiteral(" %1(creator, /* finalize */ true);") + .arg(current.completeComponent.name); + current.init.body << QStringLiteral(" %1(creator, /* finalize */ true);") + .arg(current.finalizeComponent.name); + current.init.body << QStringLiteral(" %1(creator);") + .arg(current.handleOnCompleted.name); + current.init.body << u"}"_s; + } + current.init.body << u"return context;"_s; + }; + + return QScopeGuard(generateFinalLines); +} + +/*! + \internal + + Generates \a{current.init}'s code in case when \a type is a top-level + Component type. The init method in this case mimics + QQmlObjectCreator::createComponent() logic. + + \sa generate_initCode +*/ +inline void +QmltcCodeGenerator::generate_initCodeForTopLevelComponent(QmltcType ¤t, + const QQmlJSScope::ConstPtr &type) +{ + Q_UNUSED(type); + + using namespace Qt::StringLiterals; + + // since we create a document root as QQmlComponent, we only need to fake + // QQmlComponent construction in init: + current.init.body << u"// init QQmlComponent: see QQmlObjectCreator::createComponent()"_s; + current.init.body << u"{"_s; + // we already called QQmlComponent(parent) constructor. now we need: + // 1. QQmlComponent(engine, parent) logic: + current.init.body << u"// QQmlComponent(engine, parent):"_s; + current.init.body << u"auto d = QQmlComponentPrivate::get(this);"_s; + current.init.body << u"Q_ASSERT(d);"_s; + current.init.body << u"d->engine = engine;"_s; + current.init.body << u"QObject::connect(engine, &QObject::destroyed, this, [d]() {"_s; + current.init.body << u" d->state.creator.reset();"_s; + current.init.body << u" d->engine = nullptr;"_s; + current.init.body << u"});"_s; + // 2. QQmlComponent(engine, compilationUnit, start, parent) logic: + current.init.body << u"// QQmlComponent(engine, compilationUnit, start, parent):"_s; + current.init.body + << u"auto compilationUnit = QQmlEnginePrivate::get(engine)->compilationUnitFromUrl(" + + QmltcCodeGenerator::urlMethodName() + u"());"; + current.init.body << u"d->compilationUnit = compilationUnit;"_s; + current.init.body << u"d->start = 0;"_s; + current.init.body << u"d->url = compilationUnit->finalUrl();"_s; + current.init.body << u"d->progress = 1.0;"_s; + // 3. QQmlObjectCreator::createComponent() logic which is left: + current.init.body << u"// QQmlObjectCreator::createComponent():"_s; + current.init.body << u"d->creationContext = context;"_s; + current.init.body << u"Q_ASSERT(QQmlData::get(this, /*create*/ false));"_s; + current.init.body << u"}"_s; +} + +/*! + \internal + + A generic helper function that generates special qmltc instruction code + boilerplate, adding it to a passed \a function. This is a building block + used to generate e.g. QML_endInit code. +*/ +inline void QmltcCodeGenerator::generate_qmltcInstructionCallCode( + QmltcMethod *function, const QQmlJSScope::ConstPtr &type, + const QString &baseInstructionArgs, const QString &childInstructionArgs) const +{ + using namespace Qt::StringLiterals; + + if (auto base = type->baseType(); base->isComposite()) { + function->body << u"// call base's method"_s; + const auto isCurrentType = [&](const QQmlJSScope::ConstPtr &qmlType) { + return qmlType == type; + }; + const QString creationOffset = + generate_typeCount(isCurrentType, type->enclosingInlineComponentName()); + function->body << u"{"_s; + function->body << u"QQmltcObjectCreationHelper subCreator(creator, %1);"_s.arg( + creationOffset); + if (!baseInstructionArgs.isEmpty()) { + function->body << u"%1::%2(&subCreator, %3);"_s.arg( + base->internalName(), function->name, baseInstructionArgs); + } else { + function->body << u"%1::%2(&subCreator);"_s.arg(base->internalName(), function->name); + } + function->body << u"}"_s; + } + + const bool isDocumentRoot = type == visitor->result(); + const bool isInlineComponent = type->isInlineComponent(); + + if (!(isDocumentRoot + || isInlineComponent)) // document/inline component root does all the work here + return; + auto name = isInlineComponent + ? InlineComponentOrDocumentRootName(*type->inlineComponentName()) + : InlineComponentOrDocumentRootName(QQmlJSScope::RootDocumentNameType()); + const auto types = visitor->pureQmlTypes(name); + function->body << u"// call children's methods"_s; + for (qsizetype i = 1; i < types.size(); ++i) { + const auto &type = types[i]; + Q_ASSERT(!type->isComponentRootElement()); + function->body << u"creator->get<%1>(%2)->%3(%4);"_s.arg( + type->internalName(), QString::number(i), function->name, childInstructionArgs); + } + function->body << u"// call own method code"_s; +} + +/*! + \internal + + Generates \a{current.endInit}'s code. The endInit method creates bindings, + connects signals with slots and generally performs other within-object + initialization. Additionally, the QML document root's endInit calls endInit + methods of all the necessary QML types within the document. +*/ +inline void QmltcCodeGenerator::generate_endInitCode(QmltcType ¤t, + const QQmlJSScope::ConstPtr &type) const +{ + using namespace Qt::StringLiterals; + + // QML_endInit()'s parameters: + // * QQmltcObjectCreationHelper* creator + // * QQmlEngine* engine + current.endInit.body << u"Q_UNUSED(creator)"_s; + current.endInit.body << u"Q_UNUSED(engine)"_s; + + generate_qmltcInstructionCallCode(¤t.endInit, type, u"engine"_s, u"creator, engine"_s); + + if (visitor->hasDeferredBindings(type)) { + current.endInit.body << u"{ // defer bindings"_s; + current.endInit.body << u"auto ddata = QQmlData::get(this);"_s; + current.endInit.body << u"auto thisContext = ddata->outerContext;"_s; + current.endInit.body << u"Q_ASSERT(thisContext);"_s; + current.endInit.body << QStringLiteral("ddata->deferData(%1, " + "QQmlEnginePrivate::get(engine)->" + "compilationUnitFromUrl(%2()), thisContext);") + .arg(QString::number(visitor->qmlIrObjectIndex(type)), + QmltcCodeGenerator::urlMethodName()); + current.endInit.body << u"}"_s; + } +} + +/*! + \internal + + Generates \a{current.setComplexBindings}'s code. The setComplexBindings + method creates complex bindings (such as script bindings). Additionally, the + QML document root's setComplexBindings calls setComplexBindings methods of + all the necessary QML types within the document. +*/ +inline void +QmltcCodeGenerator::generate_setComplexBindingsCode(QmltcType ¤t, + const QQmlJSScope::ConstPtr &type) const +{ + using namespace Qt::StringLiterals; + + // QML_setComplexBindings()'s parameters: + // * QQmltcObjectCreationHelper* creator + // * QQmlEngine* engine + current.setComplexBindings.body << u"Q_UNUSED(creator)"_s; + current.setComplexBindings.body << u"Q_UNUSED(engine)"_s; + + generate_qmltcInstructionCallCode(¤t.setComplexBindings, type, u"engine"_s, + u"creator, engine"_s); +} + +/*! + \internal + + A generic helper function that generates interface code boilerplate, adding + it to a passed \a function. This is a building block used to generate e.g. + QQmlParserStatus API calls. +*/ +inline void QmltcCodeGenerator::generate_interfaceCallCode(QmltcMethod *function, + const QQmlJSScope::ConstPtr &type, + const QString &interfaceName, + const QString &interfaceCall) const +{ + using namespace Qt::StringLiterals; + + // function's parameters: + // * QQmltcObjectCreationHelper* creator + // * bool canFinalize [optional, when document root or inline component root] + const bool isDocumentRoot = type == visitor->result(); + const bool isInlineComponent = type->isInlineComponent(); + function->body << u"Q_UNUSED(creator)"_s; + if (isDocumentRoot || isInlineComponent) + function->body << u"Q_UNUSED(canFinalize)"_s; + + if (auto base = type->baseType(); base->isComposite()) { + function->body << u"// call base's method"_s; + const auto isCurrentType = [&](const QQmlJSScope::ConstPtr &qmlType) { + return qmlType == type; + }; + const QString creationOffset = + generate_typeCount(isCurrentType, type->enclosingInlineComponentName()); + function->body << u"{"_s; + function->body << u"QQmltcObjectCreationHelper subCreator(creator, %1);"_s.arg( + creationOffset); + function->body << u"%1::%2(&subCreator, /* finalize */ false);"_s.arg(base->internalName(), + function->name); + function->body << u"}"_s; + } + + if (!(isDocumentRoot || isInlineComponent)) + return; + + auto name = isInlineComponent + ? InlineComponentOrDocumentRootName(*type->inlineComponentName()) + : InlineComponentOrDocumentRootName(QQmlJSScope::RootDocumentNameType()); + + const auto types = visitor->pureQmlTypes(name); + function->body << u"// call children's methods"_s; + for (qsizetype i = 1; i < types.size(); ++i) { + const auto &type = types[i]; + Q_ASSERT(!type->isComponentRootElement()); + function->body << u"{"_s; + function->body << u"auto child = creator->get<%1>(%2);"_s.arg(type->internalName(), + QString::number(i)); + function->body << u"child->%1(creator);"_s.arg(function->name); + if (type->hasInterface(interfaceName)) { + function->body << u"static_assert(std::is_base_of<%1, %2>::value);"_s.arg( + interfaceName, type->internalName()); + function->body << u"child->%1();"_s.arg(interfaceCall); + } + function->body << u"}"_s; + } + + if (type->hasInterface(interfaceName)) { + function->body << u"if (canFinalize) {"_s; + function->body << u" // call own method"_s; + function->body << u" static_assert(std::is_base_of<%1, %2>::value);"_s.arg( + interfaceName, type->internalName()); + function->body << u" this->%1();"_s.arg(interfaceCall); + function->body << u"}"_s; + } +} + +/*! + \internal + + Generates \a{current.beginClass}'s code. The beginClass method optionally + calls QQmlParserStatus::classBegin() when \a type implements the + corresponding interface. +*/ +inline void QmltcCodeGenerator::generate_beginClassCode(QmltcType ¤t, + const QQmlJSScope::ConstPtr &type) const +{ + using namespace Qt::StringLiterals; + generate_interfaceCallCode(¤t.beginClass, type, u"QQmlParserStatus"_s, u"classBegin"_s); +} + +/*! + \internal + + Generates \a{current.completeComponent}'s code. The completeComponent method + optionally calls QQmlParserStatus::componentComplete() when \a type + implements the corresponding interface. +*/ +inline void +QmltcCodeGenerator::generate_completeComponentCode(QmltcType ¤t, + const QQmlJSScope::ConstPtr &type) const +{ + using namespace Qt::StringLiterals; + generate_interfaceCallCode(¤t.completeComponent, type, u"QQmlParserStatus"_s, + u"componentComplete"_s); +} + +/*! + \internal + + Generates \a{current.finalizeComponent}'s code. The finalizeComponent method + optionally calls QQmlFinalizerHook::componentFinalized() when \a type + implements the corresponding interface. +*/ +inline void +QmltcCodeGenerator::generate_finalizeComponentCode(QmltcType ¤t, + const QQmlJSScope::ConstPtr &type) const +{ + using namespace Qt::StringLiterals; + generate_interfaceCallCode(¤t.finalizeComponent, type, u"QQmlFinalizerHook"_s, + u"componentFinalized"_s); +} + +/*! + \internal + + Generates \a{current.handleOnCompleted}'s code. The handleOnCompleted method + optionally calls a Component.onCompleted handler if that is present in \a + type. +*/ +inline void +QmltcCodeGenerator::generate_handleOnCompletedCode(QmltcType ¤t, + const QQmlJSScope::ConstPtr &type) const +{ + using namespace Qt::StringLiterals; + + // QML_handleOnCompleted()'s parameters: + // * QQmltcObjectCreationHelper* creator + current.handleOnCompleted.body << u"Q_UNUSED(creator)"_s; + + generate_qmltcInstructionCallCode(¤t.handleOnCompleted, type, QString(), u"creator"_s); +} + +/*! + \internal + + Generates a constexpr function consisting of a sum of type counts for a + current QML document. Predicate \a p acts as a stop condition to prematurely + end the sum generation. + + The high-level idea: + + Each qmltc-compiled document root has a notion of type count. Type count is + a number of types the current QML document contains (except for + Component-wrapped types) plus the sum of all type counts of all the QML + documents used in the current document: if current document has a type with + QML base type, this type's type count is added to the type count of the + current document. + + To be able to lookup created objects during the creation process, one needs + to know an index of each object within the document + an offset of the + document. Index comes from QmltcVisitor and is basically a serial number of + a type in the document (index < type count of the document root type). The + offset is more indirect. + + The current document always starts with an offset of 0, each type that has a + QML base type also "has a sub-document". Each sub-document has a non-0 + offset X, where X is calculated as a sum of the current document's type + count and a cumulative type count of all the previous sub-documents that + appear before the sub-document of interest: + + \code + // A.qml + Item { // offset: 0; number of types == 1 (document root) + 3 (children) + + QmlBase1 { } // offset: 4 (number of types in A.qml itself) + + QmlBase2 { } // offset: 4 + N, where N == typeCount(QmlBase1.qml) + + QmlBase3 { } // offset: (4 + N) + M, where M == typeCount(QmlBase2.qml) + + } // typeCount(A.qml) == 4 + N + M + O, where O == typeCount(QmlBase3.qml) + \endcode + + As all objects are put into an array, schematically you can look at it in + the following way: + + ``` + count: 4 N M O + objects: aaaa|xxxxxxxxxxxxx|yyyyyyy|zzz + ^ ^ ^ ^ + files: | QmlBase1.qml | QmlBase3.qml + A.qml QmlBase2.qml + ``` + + For the object lookup logic itself, see QQmltcObjectCreationHelper +*/ +template<typename Predicate> +inline QString QmltcCodeGenerator::generate_typeCount( + Predicate p, const InlineComponentOrDocumentRootName &inlinedComponent) const +{ + using namespace Qt::StringLiterals; + + const QList<QQmlJSScope::ConstPtr> typesWithBaseTypeCount = + visitor->qmlTypesWithQmlBases(inlinedComponent); + QStringList components; + components.reserve(1 + typesWithBaseTypeCount.size()); + + Q_ASSERT(visitor->pureQmlTypes(inlinedComponent).size() > 0); + Q_ASSERT(visitor->typeCount(inlinedComponent) + >= visitor->pureQmlTypes(inlinedComponent).size()); + qsizetype typeCount = visitor->typeCount(inlinedComponent); + + // add this document's type counts minus document root (if not an inline component) + if (std::holds_alternative<RootDocumentNameType>(inlinedComponent)) + typeCount--; + components << QString::number(typeCount); + + // traverse types with QML base classes + for (const QQmlJSScope::ConstPtr &t : typesWithBaseTypeCount) { + if (p(t)) + break; + QString typeCountTemplate = u"QQmltcObjectCreationHelper::typeCount<%1>()"_s; + if (t == visitor->result()) { // t is this document's root + components << typeCountTemplate.arg(t->baseTypeName()); + } else if (t->isInlineComponent()) { + // inline components always have a base class, by definition + Q_ASSERT(t->baseType()); + components << typeCountTemplate.arg(t->baseType()->internalName()); + } else { + components << typeCountTemplate.arg(t->internalName()); + } + } + + return components.join(u" + "_s); +} + +QT_END_NAMESPACE + +#endif // QMLTCCOMPILERPIECES_H diff --git a/tools/qmltc/qmltcoutputir.h b/tools/qmltc/qmltcoutputir.h new file mode 100644 index 0000000000..de531f718d --- /dev/null +++ b/tools/qmltc/qmltcoutputir.h @@ -0,0 +1,195 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef QMLTCOUTPUTIR_H +#define QMLTCOUTPUTIR_H + +#include <QtCore/qstring.h> +#include <QtCore/qlist.h> +#include <QtCore/qstringlist.h> +#include <QtCore/qset.h> + +#include <private/qqmljsmetatypes_p.h> + +#include <optional> + +QT_BEGIN_NAMESPACE + +// Below are the classes that represent compiled QML types in a string data +// form. These classes are used to generate C++ code. + +// Represents C++ variable +struct QmltcVariable +{ + QString cppType; // C++ type of a variable + QString name; // variable name + QString defaultValue; // optional initialization value + + QmltcVariable() = default; + // special ctor for QList's emplace back + QmltcVariable(const QString &t, const QString &n, const QString &v = QString()) + : cppType(t), name(n), defaultValue(v) + { + } +}; + +struct QmltcProperty : QmltcVariable +{ + QString containingClass; + QString signalName; + + QmltcProperty() = default; + QmltcProperty(const QString t, const QString &n, const QString &c, const QString &s) + : QmltcVariable(t, n), containingClass(c), signalName(s) + { + } +}; + +// Represents QML -> C++ compiled enumeration type +struct QmltcEnum +{ + QString cppType; // C++ type of an enum + QStringList keys; // enumerator keys + QStringList values; // enumerator values + QString ownMocLine; // special MOC line that follows enum declaration + + QmltcEnum() = default; + QmltcEnum(const QString &t, const QStringList &ks, const QStringList &vs, const QString &l) + : cppType(t), keys(ks), values(vs), ownMocLine(l) + { + } +}; + +struct QmltcMethodBase +{ + QStringList comments; // C++ comments + QString name; // C++ function name + QList<QmltcVariable> parameterList; // C++ function parameter list + QStringList body; // C++ function code + QQmlJSMetaMethod::Access access = QQmlJSMetaMethod::Public; // access specifier + QStringList declarationPrefixes; + QStringList modifiers; // cv-qualifiers, ref-qualifier, noexcept, attributes +}; + +// Represents QML -> C++ compiled function +struct QmltcMethod : QmltcMethodBase +{ + QString returnType; // C++ return type + QQmlJSMetaMethodType type = QQmlJSMetaMethodType::Method; // Qt function type + + // TODO: should be a better way to handle this + bool userVisible = false; // tells if a function is prioritized during the output generation +}; + +// Represents C++ ctor of a type +struct QmltcCtor : QmltcMethodBase +{ + QStringList initializerList; // C++ ctor's initializer list +}; + +// Represents C++ dtor of a type +struct QmltcDtor : QmltcMethodBase +{ +}; + +// Represents a generated class that knows how to set the public, +// writable properties of a compiled QML -> C++ type. +// This is generally intended to be available for the root of the +// document to allow the user to set the initial values for +// properties, when creating a component, with support for strong +// typing. +struct QmltcPropertyInitializer { + QString name; + + QmltcCtor constructor; + + // A member containing a reference to the object for which the + // properties should be set. + QmltcVariable component; + + // A member containing a cache of properties that were actually + // set that can be referenced later.. + QmltcVariable initializedCache; + + // Setter methods for each property. + QList<QmltcMethod> propertySetters; +}; + +// Represents a generated class that contains a bundle of values to +// initialize the required properties of a type. +// +// This is generally intended to be available for the root component +// of the document, where it will be used as a constructor argument to +// force the user to provide initial values for the required +// properties of the constructed type. +struct QmltcRequiredPropertiesBundle { + QString name; + + QList<QmltcVariable> members; +}; + +// Represents QML -> C++ compiled type +struct QmltcType +{ + QString cppType; // C++ type of the QML type + QStringList baseClasses; // C++ type names of base classes + QStringList mocCode; // Qt MOC code + QStringList otherCode; // Random code that doesn't fit any category, e.g. friend declarations + + // member types: enumerations and child types + QList<QmltcEnum> enums; + QList<QmltcType> children; // these are pretty much always empty + + // special member functions: + QmltcCtor baselineCtor {}; // does basic contruction + QmltcCtor externalCtor {}; // calls basicCtor, calls init + QmltcMethod init {}; // starts object initialization (context setup), calls finalize + QmltcMethod beginClass {}; // calls QQmlParserStatus::classBegin() + QmltcMethod endInit {}; // ends object initialization (with "simple" bindings setup) + QmltcMethod setComplexBindings {}; // sets up "complex" (e.g. script) bindings + QmltcMethod completeComponent {}; // calls QQmlParserStatus::componentComplete() + QmltcMethod finalizeComponent {}; // calls QQmlFinalizerHook::componentFinalized() + QmltcMethod handleOnCompleted {}; // calls Component.onCompleted + + std::optional<QmltcDtor> dtor {}; + + // member functions: methods, signals and slots + QList<QmltcMethod> functions; + // member variables + QList<QmltcVariable> variables; + QList<QmltcProperty> properties; + + // QML document root specific: + std::optional<QmltcMethod> typeCount; // the number of QML types defined in a document + + // TODO: only needed for binding callables - should not be needed, generally + bool ignoreInit = false; // specifies whether init and externalCtor should be ignored + + // needed for singletons + std::optional<QmltcMethod> staticCreate{}; + + // A proxy class that provides a restricted interface that only + // allows setting the properties of the type. + QmltcPropertyInitializer propertyInitializer{}; + + std::optional<QmltcRequiredPropertiesBundle> requiredPropertiesBundle{}; +}; + +// Represents whole QML program, compiled to C++ +struct QmltcProgram +{ + QString url; // QML file url + QString cppPath; // C++ output .cpp path + QString hPath; // C++ output .h path + QString outNamespace; + QString exportMacro; // if not empty, the macro that should be used to export the generated + // classes + QSet<QString> includes; // non-default C++ include files + QmltcMethod urlMethod; // returns QUrl of the QML document + + QList<QmltcType> compiledTypes; // all QML types that are compiled to C++ +}; + +QT_END_NAMESPACE + +#endif // QMLTCOUTPUTIR_H diff --git a/tools/qmltc/qmltcoutputprimitives.h b/tools/qmltc/qmltcoutputprimitives.h new file mode 100644 index 0000000000..dae523b0d7 --- /dev/null +++ b/tools/qmltc/qmltcoutputprimitives.h @@ -0,0 +1,100 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef QMLTCOUTPUTPRIMITIVES_H +#define QMLTCOUTPUTPRIMITIVES_H + +#include <QtCore/qstack.h> +#include <QtCore/qstring.h> +#include <QtCore/qstringbuilder.h> + +QT_BEGIN_NAMESPACE + +struct QmltcOutput +{ + QString header; + QString cpp; +}; + +class QmltcOutputWrapper +{ + QmltcOutput &m_code; + + template<typename String> + static void rawAppend(QString &out, const String &what, int extraIndent = 0) + { + constexpr char16_t newLine[] = u"\n"; + out += QString(extraIndent * 4, u' ') + what + newLine; + } + +public: + QmltcOutputWrapper(QmltcOutput &code) : m_code(code) { } + const QmltcOutput &code() const { return m_code; } + + QStack<QString> memberScopes; // member name scopes e.g. MyClass::MySubclass:: + int headerIndent = 0; // header indentation level + int cppIndent = 0; // cpp indentation level + + // manages current scope of the generated code, which is necessary for + // cpp file generation. Example: + // class MyClass { MyClass(); }; - in header + // MyClass::MyClass() {} - in cpp + // MemberNameScope makes sure "MyClass::" is recorded + struct MemberNameScope + { + QmltcOutputWrapper *m_code; + MemberNameScope(QmltcOutputWrapper *code, const QString &str) : m_code(code) + { + m_code->memberScopes.push(str); + } + ~MemberNameScope() { m_code->memberScopes.pop(); } + Q_DISABLE_COPY_MOVE(MemberNameScope) + }; + + struct HeaderIndentationScope + { + QmltcOutputWrapper *m_code; + HeaderIndentationScope(QmltcOutputWrapper *code) : m_code(code) { ++m_code->headerIndent; } + ~HeaderIndentationScope() { --m_code->headerIndent; } + Q_DISABLE_COPY_MOVE(HeaderIndentationScope) + }; + + struct CppIndentationScope + { + QmltcOutputWrapper *m_code; + CppIndentationScope(QmltcOutputWrapper *code) : m_code(code) { ++m_code->cppIndent; } + ~CppIndentationScope() { --m_code->cppIndent; } + Q_DISABLE_COPY_MOVE(CppIndentationScope) + }; + + // appends string \a what with extra indentation \a extraIndent to current + // header string + template<typename String> + void rawAppendToHeader(const String &what, int extraIndent = 0) + { + rawAppend(m_code.header, what, headerIndent + extraIndent); + } + + // appends string \a what with extra indentation \a extraIndent to current + // cpp string + template<typename String> + void rawAppendToCpp(const String &what, int extraIndent = 0) + { + rawAppend(m_code.cpp, what, cppIndent + extraIndent); + } + + // special case of rawAppendToCpp that makes sure that string "foo()" + // becomes "MyClass::foo()" + template<typename String> + void rawAppendSignatureToCpp(const String &what, int extraIndent = 0) + { + QString signatureScope; + for (const auto &scope : memberScopes) + signatureScope += scope + u"::"; + rawAppendToCpp(signatureScope + what, extraIndent); + } +}; + +QT_END_NAMESPACE + +#endif // QMLTCOUTPUTPRIMITIVES_H diff --git a/tools/qmltc/qmltcpropertyutils.h b/tools/qmltc/qmltcpropertyutils.h new file mode 100644 index 0000000000..8a69e5ef09 --- /dev/null +++ b/tools/qmltc/qmltcpropertyutils.h @@ -0,0 +1,55 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef QMLTCPROPERTYUTILS_H +#define QMLTCPROPERTYUTILS_H + +#include <private/qqmljsmetatypes_p.h> +#include <private/qqmljsscope_p.h> +#include <QtQml/private/qqmlsignalnames_p.h> + +QT_BEGIN_NAMESPACE + +/*! + \internal + + Returns an underlying C++ type of \a p property. +*/ +inline QString getUnderlyingType(const QQmlJSMetaProperty &p) +{ + if (p.isList()) { + // We cannot just use p.type()->internalName() here because it may be + // a list property of something that only receives a C++ name from qmltc. + const QQmlJSScope::ConstPtr valueType = p.type()->valueType(); + return (valueType->isReferenceType() ? u"QQmlListProperty<" : u"QList<") + + valueType->internalName() + u'>'; + } + + return p.type()->augmentedInternalName(); +} + +// simple class that, for a given property, creates information for the +// Q_PROPERTY macro (READ/WRITE function names, etc.) +struct QmltcPropertyData +{ + QmltcPropertyData(const QQmlJSMetaProperty &p) : QmltcPropertyData(p.propertyName()) { } + + QmltcPropertyData(const QString &propertyName) + { + read = propertyName; + write = QQmlSignalNames::addPrefixToPropertyName(u"set", propertyName); + bindable = QQmlSignalNames::addPrefixToPropertyName(u"bindable", propertyName); + notify = QQmlSignalNames::propertyNameToChangedSignalName(propertyName); + reset = QQmlSignalNames::addPrefixToPropertyName(u"reset", propertyName); + } + + QString read; + QString write; + QString bindable; + QString notify; + QString reset; +}; + +QT_END_NAMESPACE + +#endif // QMLTCPROPERTYUTILS_H diff --git a/tools/qmltc/qmltctyperesolver.cpp b/tools/qmltc/qmltctyperesolver.cpp new file mode 100644 index 0000000000..a7bf9debac --- /dev/null +++ b/tools/qmltc/qmltctyperesolver.cpp @@ -0,0 +1,56 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qmltctyperesolver.h" + +#include <private/qqmljsimporter_p.h> +#include <private/qqmljsliteralbindingcheck_p.h> +#include <private/qv4value_p.h> + +#include <QtCore/qqueue.h> +#include <QtCore/qloggingcategory.h> +#include <QtCore/qfileinfo.h> +#include <QtCore/qdiriterator.h> + +Q_LOGGING_CATEGORY(lcTypeResolver2, "qml.qmltc.typeresolver", QtInfoMsg); + +void QmltcTypeResolver::init(QmltcVisitor *visitor, QQmlJS::AST::Node *program) +{ + QQmlJSTypeResolver::init(visitor, program); + + m_root = visitor->result(); + + QQueue<QQmlJSScope::Ptr> objects; + objects.enqueue(m_root); + while (!objects.isEmpty()) { + const QQmlJSScope::Ptr object = objects.dequeue(); + const QQmlJS::SourceLocation location = object->sourceLocation(); + qCDebug(lcTypeResolver2()).nospace() << "inserting " << object.data() << " at " + << location.startLine << ':' << location.startColumn; + m_objectsByLocationNonConst.insert({ location.startLine, location.startColumn }, object); + + const auto childScopes = object->childScopes(); + for (const auto &childScope : childScopes) + objects.enqueue(childScope); + } +} + +QQmlJSScope::Ptr +QmltcTypeResolver::scopeForLocation(const QV4::CompiledData::Location &location) const +{ + qCDebug(lcTypeResolver2()).nospace() + << "looking for object at " << location.line() << ':' << location.column(); + return m_objectsByLocationNonConst.value(location); +} + +QPair<QString, QQmlJSScope::Ptr> +QmltcTypeResolver::importedType(const QQmlJSScope::ConstPtr &type) const +{ + const auto files = m_importer->importedFiles(); + auto it = std::find_if(files.cbegin(), files.cend(), [&](const QQmlJSScope::Ptr &importedType) { + return importedType.data() == type.data(); + }); + if (it == files.cend()) + return {}; + return { it.key(), it.value() }; +} diff --git a/tools/qmltc/qmltctyperesolver.h b/tools/qmltc/qmltctyperesolver.h new file mode 100644 index 0000000000..19a3ee01bc --- /dev/null +++ b/tools/qmltc/qmltctyperesolver.h @@ -0,0 +1,40 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef QMLTCTYPERESOLVER_H +#define QMLTCTYPERESOLVER_H + +#include "qmltcvisitor.h" + +#include <QtQml/private/qqmlirbuilder_p.h> +#include <private/qqmljstyperesolver_p.h> +#include <private/qqmljsimporter_p.h> +#include <private/qqmljslogger_p.h> + +QT_BEGIN_NAMESPACE + +class QmltcTypeResolver : public QQmlJSTypeResolver +{ +public: + QmltcTypeResolver(QQmlJSImporter *importer) : QQmlJSTypeResolver(importer), m_importer(importer) + { + Q_ASSERT(importer); + } + + void init(QmltcVisitor *visitor, QQmlJS::AST::Node *program); + + QQmlJSScope::Ptr scopeForLocation(const QV4::CompiledData::Location &location) const; + + // returns an import pair {url, modifiable type} for a given \a type + QPair<QString, QQmlJSScope::Ptr> importedType(const QQmlJSScope::ConstPtr &type) const; + +private: + QQmlJSImporter *m_importer = nullptr; + + QHash<QV4::CompiledData::Location, QQmlJSScope::Ptr> m_objectsByLocationNonConst; + QQmlJSScope::Ptr m_root; +}; + +QT_END_NAMESPACE + +#endif // QMLTCTYPERESOLVER_H diff --git a/tools/qmltc/qmltcvisitor.cpp b/tools/qmltc/qmltcvisitor.cpp new file mode 100644 index 0000000000..119308ef65 --- /dev/null +++ b/tools/qmltc/qmltcvisitor.cpp @@ -0,0 +1,871 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qmltcvisitor.h" +#include "qmltcpropertyutils.h" + +#include <QtCore/qfileinfo.h> +#include <QtCore/qstack.h> +#include <QtCore/qdir.h> +#include <QtCore/qloggingcategory.h> +#include <QtQml/private/qqmlsignalnames_p.h> + +#include <private/qqmljsutils_p.h> + +#include <algorithm> + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +Q_DECLARE_LOGGING_CATEGORY(lcQmltcCompiler) + +static QString uniqueNameFromPieces(const QStringList &pieces, QHash<QString, int> &repetitions) +{ + QString possibleName = pieces.join(u'_'); + const int count = repetitions[possibleName]++; + if (count > 0) + possibleName.append(u"_" + QString::number(count)); + return possibleName; +} + +static bool isExplicitComponent(const QQmlJSScope::ConstPtr &type) +{ + if (!type->isComposite()) + return false; + auto base = type->baseType(); + return base && base->internalName() == u"QQmlComponent"; +} + +/*! \internal + Returns if type is an implicit component. + This method should only be called after implicit components + are detected, that is, after QQmlJSImportVisitor::endVisit(UiProgram *) + was called. + */ +static bool isImplicitComponent(const QQmlJSScope::ConstPtr &type) +{ + if (!type->isComposite()) + return false; + const auto cppBase = QQmlJSScope::nonCompositeBaseType(type); + const bool isComponentBased = (cppBase && cppBase->internalName() == u"QQmlComponent"); + return type->isComponentRootElement() && !isComponentBased; +} + +/*! \internal + Checks if type is inside or a (implicit or explicit) component. + This method should only be called after implicit components + are detected, that is, after QQmlJSImportVisitor::endVisit(UiProgram *) + was called. + */ +static bool isOrUnderComponent(QQmlJSScope::ConstPtr type) +{ + Q_ASSERT(type->isComposite()); // we're dealing with composite types here + for (; type; type = type->parentScope()) { + if (isExplicitComponent(type) || isImplicitComponent(type)) { + return true; + } + } + return false; +} + +QmltcVisitor::QmltcVisitor(const QQmlJSScope::Ptr &target, QQmlJSImporter *importer, + QQmlJSLogger *logger, const QString &implicitImportDirectory, + const QStringList &qmldirFiles) + : QQmlJSImportVisitor(target, importer, logger, implicitImportDirectory, qmldirFiles) +{ + m_qmlTypeNames.append(QFileInfo(logger->fileName()).baseName()); // put document root +} + +void QmltcVisitor::findCppIncludes() +{ + // TODO: this pass is slow: we have to do exhaustive search because some C++ + // code could do forward declarations and they are hard to handle correctly + QSet<const QQmlJSScope *> visitedTypes; // we can still improve by walking all types only once + const auto visitType = [&visitedTypes](const QQmlJSScope::ConstPtr &type) -> bool { + if (visitedTypes.contains(type.data())) + return true; + visitedTypes.insert(type.data()); + return false; + }; + const auto addCppInclude = [this](const QQmlJSScope::ConstPtr &type) { + if (QString includeFile = type->filePath(); includeFile.endsWith(u".h")) + m_cppIncludes.insert(std::move(includeFile)); + }; + + const auto findInType = [&](const QQmlJSScope::ConstPtr &type) { + if (!type) + return; + if (visitType(type)) // optimization - don't call nonCompositeBaseType() needlessly + return; + + // look in type + addCppInclude(type); + + if (type->isListProperty()) + addCppInclude(type->valueType()); + + // look in type's base type + auto base = type->baseType(); + if (!base && type->isComposite()) + // in this case, qqmljsimportvisitor would have already print an error message + // about the missing type, so just return silently without crashing + return; + if (!base || visitType(base)) + return; + addCppInclude(base); + }; + + const auto constructPrivateInclude = [](QStringView publicInclude) -> QString { + if (publicInclude.isEmpty()) + return QString(); + Q_ASSERT(publicInclude.endsWith(u".h"_s) || publicInclude.endsWith(u".hpp"_s)); + const qsizetype dotLocation = publicInclude.lastIndexOf(u'.'); + QStringView extension = publicInclude.sliced(dotLocation); + QStringView includeWithoutExtension = publicInclude.first(dotLocation); + // check if "public" include is in fact already private + if (publicInclude.startsWith(u"private")) + return includeWithoutExtension + u"_p" + extension; + return u"private/" + includeWithoutExtension + u"_p" + extension; + }; + + // walk the whole type hierarchy + QStack<QQmlJSScope::ConstPtr> types; + types.push(m_exportedRootScope); + while (!types.isEmpty()) { + auto type = types.pop(); + Q_ASSERT(type); + + const auto scopeType = type->scopeType(); + if (scopeType != QQmlSA::ScopeType::QMLScope + && scopeType != QQmlSA::ScopeType::GroupedPropertyScope + && scopeType != QQmlSA::ScopeType::AttachedPropertyScope) { + continue; + } + + for (auto t = type; !type->isArrayScope() && t; t = t->baseType()) { + findInType(t); + + // look in properties + const auto properties = t->ownProperties(); + for (const QQmlJSMetaProperty &p : properties) { + findInType(p.type()); + + if (p.isPrivate() && t->filePath().endsWith(u".h")) { + const QString ownersInclude = t->filePath(); + QString privateInclude = constructPrivateInclude(ownersInclude); + if (!privateInclude.isEmpty()) + m_cppIncludes.insert(std::move(privateInclude)); + } + } + + // look in methods + const auto methods = t->ownMethods(); + for (const QQmlJSMetaMethod &m : methods) { + findInType(m.returnType()); + + const auto parameters = m.parameters(); + for (const auto ¶m : parameters) + findInType(param.type()); + } + } + + types.append(type->childScopes()); + } + + // remove own include + m_cppIncludes.remove(m_exportedRootScope->filePath()); +} + +static void addCleanQmlTypeName(QStringList *names, const QQmlJSScope::ConstPtr &scope) +{ + Q_ASSERT(scope->scopeType() == QQmlSA::ScopeType::QMLScope); + Q_ASSERT(!scope->isArrayScope()); + Q_ASSERT(!scope->baseTypeName().isEmpty()); + // the scope is guaranteed to be a new QML type, so any prefixes (separated + // by dots) should be import namespaces + const std::optional<QString> &inlineComponentName = scope->inlineComponentName(); + QString name = inlineComponentName ? *inlineComponentName : scope->baseTypeName(); + names->append(name.replace(u'.', u'_')); +} + +bool QmltcVisitor::visit(QQmlJS::AST::UiObjectDefinition *object) +{ + const bool processingRoot = !rootScopeIsValid(); + + if (!QQmlJSImportVisitor::visit(object)) + return false; + + if (processingRoot || m_currentScope->isInlineComponent()) { + Q_ASSERT(rootScopeIsValid()); + setRootFilePath(); + } + + // we're not interested in non-QML scopes + if (m_currentScope->scopeType() != QQmlSA::ScopeType::QMLScope) + return true; + + if (m_currentScope->isInlineComponent()) { + m_inlineComponentNames.append(m_currentRootName); + m_inlineComponents[m_currentRootName] = m_currentScope; + } + + if (m_currentScope != m_exportedRootScope) // not document root + addCleanQmlTypeName(&m_qmlTypeNames, m_currentScope); + // give C++-relevant internal names to QMLScopes, we can use them later in compiler + m_currentScope->setInternalName(uniqueNameFromPieces(m_qmlTypeNames, m_qmlTypeNameCounts)); + m_qmlTypesWithQmlBases[m_currentRootName].append(m_currentScope); + + return true; +} + +void QmltcVisitor::endVisit(QQmlJS::AST::UiObjectDefinition *object) +{ + if (m_currentScope->scopeType() == QQmlSA::ScopeType::QMLScope) + m_qmlTypeNames.removeLast(); + QQmlJSImportVisitor::endVisit(object); +} + +bool QmltcVisitor::visit(QQmlJS::AST::UiObjectBinding *uiob) +{ + if (!QQmlJSImportVisitor::visit(uiob)) + return false; + + if (m_currentScope != m_exportedRootScope) // not document root + addCleanQmlTypeName(&m_qmlTypeNames, m_currentScope); + // give C++-relevant internal names to QMLScopes, we can use them later in compiler + m_currentScope->setInternalName(uniqueNameFromPieces(m_qmlTypeNames, m_qmlTypeNameCounts)); + + m_qmlTypesWithQmlBases[m_currentRootName].append(m_currentScope); + return true; +} + +void QmltcVisitor::endVisit(QQmlJS::AST::UiObjectBinding *uiob) +{ + m_qmlTypeNames.removeLast(); + QQmlJSImportVisitor::endVisit(uiob); +} + +bool QmltcVisitor::visit(QQmlJS::AST::UiPublicMember *publicMember) +{ + if (!QQmlJSImportVisitor::visit(publicMember)) + return false; + + // augment property: set its write/read/etc. methods + if (publicMember->type == QQmlJS::AST::UiPublicMember::Property) { + const auto name = publicMember->name.toString(); + + QQmlJSScope::Ptr owner = + m_savedBindingOuterScope ? m_savedBindingOuterScope : m_currentScope; + QQmlJSMetaProperty property = owner->ownProperty(name); + Q_ASSERT(property.isValid()); + if (!property.isAlias()) { // aliases are covered separately + QmltcPropertyData compiledData(property); + if (property.read().isEmpty()) + property.setRead(compiledData.read); + if (!property.isList()) { + if (property.write().isEmpty() && property.isWritable()) + property.setWrite(compiledData.write); + // Note: prefer BINDABLE to NOTIFY + if (property.bindable().isEmpty()) + property.setBindable(compiledData.bindable); + } + owner->addOwnProperty(property); + } + + const QString notifyName = QQmlSignalNames::propertyNameToChangedSignalName(name); + // also check that notify is already a method of the scope + { + auto owningScope = m_savedBindingOuterScope ? m_savedBindingOuterScope : m_currentScope; + const auto methods = owningScope->ownMethods(notifyName); + if (methods.size() != 1) { + const QString errorString = + methods.isEmpty() ? u"no signal"_s : u"too many signals"_s; + m_logger->log( + u"internal error: %1 found for property '%2'"_s.arg(errorString, name), + qmlCompiler, publicMember->identifierToken); + return false; + } else if (methods[0].methodType() != QQmlJSMetaMethodType::Signal) { + m_logger->log(u"internal error: method %1 of property %2 must be a signal"_s.arg( + notifyName, name), + qmlCompiler, publicMember->identifierToken); + return false; + } + } + } + + return true; +} + +bool QmltcVisitor::visit(QQmlJS::AST::UiScriptBinding *scriptBinding) +{ + if (!QQmlJSImportVisitor::visit(scriptBinding)) + return false; + + { + const auto id = scriptBinding->qualifiedId; + if (!id->next && id->name == QLatin1String("id")) + m_typesWithId[m_currentScope] = -1; // temporary value + } + + return true; +} + +bool QmltcVisitor::visit(QQmlJS::AST::UiInlineComponent *component) +{ + if (!QQmlJSImportVisitor::visit(component)) + return false; + return true; +} + +void QmltcVisitor::endVisit(QQmlJS::AST::UiProgram *program) +{ + QQmlJSImportVisitor::endVisit(program); + if (!rootScopeIsValid()) // in case we failed badly + return; + + QHash<QQmlJSScope::ConstPtr, QList<QQmlJSMetaPropertyBinding>> bindings; + for (const QQmlJSScope::ConstPtr &type : std::as_const(m_qmlTypes)) { + if (isOrUnderComponent(type)) + continue; + bindings.insert(type, type->ownPropertyBindingsInQmlIROrder()); + } + + postVisitResolve(bindings); + setupAliases(); + + if (m_mode != Mode::Compile) + return; + + findCppIncludes(); + + for (const QList<QQmlJSScope::ConstPtr> &qmlTypes : m_pureQmlTypes) + for (const QQmlJSScope::ConstPtr &type : qmlTypes) + checkNamesAndTypes(type); +} + +QQmlJSScope::ConstPtr fetchType(const QQmlJSMetaPropertyBinding &binding) +{ + switch (binding.bindingType()) { + case QQmlSA::BindingType::Object: + return binding.objectType(); + case QQmlSA::BindingType::Interceptor: + return binding.interceptorType(); + case QQmlSA::BindingType::ValueSource: + return binding.valueSourceType(); + // TODO: AttachedProperty and GroupProperty are not supported yet, + // but have to also be acknowledged here + default: + return {}; + } + Q_UNREACHABLE_RETURN({}); +} + +template<typename Predicate> +void iterateTypes( + const QQmlJSScope::ConstPtr &root, + const QHash<QQmlJSScope::ConstPtr, QList<QQmlJSMetaPropertyBinding>> &qmlIrOrderedBindings, + Predicate predicate) +{ + // NB: depth-first-search is used here to mimic various QmlIR passes + QStack<QQmlJSScope::ConstPtr> types; + types.push(root); + while (!types.isEmpty()) { + auto current = types.pop(); + + if (predicate(current)) + continue; + + if (isOrUnderComponent(current)) // ignore implicit/explicit components + continue; + + Q_ASSERT(qmlIrOrderedBindings.contains(current)); + const auto &bindings = qmlIrOrderedBindings[current]; + // reverse the binding order here, because stack processes right-most + // child first and we need left-most first + for (auto it = bindings.rbegin(); it != bindings.rend(); ++it) { + const auto &binding = *it; + if (auto type = fetchType(binding)) + types.push(type); + } + } +} + +template<typename Predicate> +void iterateBindings( + const QQmlJSScope::ConstPtr &root, + const QHash<QQmlJSScope::ConstPtr, QList<QQmlJSMetaPropertyBinding>> &qmlIrOrderedBindings, + Predicate predicate) +{ + // NB: depth-first-search is used here to mimic various QmlIR passes + QStack<QQmlJSScope::ConstPtr> types; + types.push(root); + while (!types.isEmpty()) { + auto current = types.pop(); + + if (isOrUnderComponent(current)) // ignore implicit/explicit components + continue; + + Q_ASSERT(qmlIrOrderedBindings.contains(current)); + const auto &bindings = qmlIrOrderedBindings[current]; + // reverse the binding order here, because stack processes right-most + // child first and we need left-most first + for (auto it = bindings.rbegin(); it != bindings.rend(); ++it) { + const auto &binding = *it; + + if (predicate(current, binding)) + continue; + + if (auto type = fetchType(binding)) + types.push(type); + } + } +} + +/*! \internal + This is a special function that must be called after + QQmlJSImportVisitor::endVisit(QQmlJS::AST::UiProgram *). It is used to + resolve things that couldn't be resolved during the AST traversal, such + as anything that is dependent on implicit or explicit components +*/ +void QmltcVisitor::postVisitResolve( + const QHash<QQmlJSScope::ConstPtr, QList<QQmlJSMetaPropertyBinding>> &qmlIrOrderedBindings) +{ + + // add the document root (that is not an inline component), as we usually + // want to iterate on all inline components, followed by the document root + m_inlineComponentNames.append(RootDocumentNameType()); + m_inlineComponents[RootDocumentNameType()] = m_exportedRootScope; + + // match scopes to indices of QmlIR::Object from QmlIR::Document + qsizetype count = 0; + const auto setIndex = [&](const QQmlJSScope::Ptr ¤t) { + if (current->scopeType() != QQmlSA::ScopeType::QMLScope || current->isArrayScope()) + return; + Q_ASSERT(!m_qmlIrObjectIndices.contains(current)); + m_qmlIrObjectIndices[current] = count; + ++count; + }; + QQmlJSUtils::traverseFollowingQmlIrObjectStructure(m_exportedRootScope, setIndex); + + // find types that are part of the deferred bindings (we care about *types* + // exclusively here) + QSet<QQmlJSScope::ConstPtr> deferredTypes; + const auto findDeferred = [&](const QQmlJSScope::ConstPtr &type, + const QQmlJSMetaPropertyBinding &binding) { + const QString propertyName = binding.propertyName(); + Q_ASSERT(!propertyName.isEmpty()); + if (type->isNameDeferred(propertyName)) { + m_typesWithDeferredBindings.insert(type); + + if (binding.hasObject() || binding.hasInterceptor() || binding.hasValueSource()) { + deferredTypes.insert(fetchType(binding)); + return true; + } + } + return false; + }; + for (const auto &inlineComponentName : m_inlineComponentNames) { + iterateBindings(m_inlineComponents[inlineComponentName], qmlIrOrderedBindings, + findDeferred); + } + + const auto isOrUnderDeferred = [&deferredTypes](QQmlJSScope::ConstPtr type) { + for (; type; type = type->parentScope()) { + if (deferredTypes.contains(type)) + return true; + } + return false; + }; + + // find all "pure" QML types + QList<QQmlJSScope::ConstPtr> explicitComponents; + for (qsizetype i = 0; i < m_qmlTypes.size(); ++i) { + const QQmlJSScope::ConstPtr &type = m_qmlTypes.at(i); + + if (isOrUnderComponent(type) || isOrUnderDeferred(type)) { + // root is special: we compile Component roots. root is also never + // deferred, so in case `isOrUnderDeferred(type)` returns true, we + // always continue here + if (type != m_exportedRootScope) { + // if a type is an explicit component, its id "leaks" into the + // document context + if (isExplicitComponent(type)) + explicitComponents.append(type); + continue; + } + } + + const InlineComponentOrDocumentRootName inlineComponent = + type->enclosingInlineComponentName(); + QList<QQmlJSScope::ConstPtr> &pureQmlTypes = m_pureQmlTypes[inlineComponent]; + m_creationIndices[type] = pureQmlTypes.size(); + pureQmlTypes.append(type); + } + + // update the typeCounts + for (const auto &inlineComponent : m_inlineComponentNames) { + m_inlineComponentTypeCount[inlineComponent] = m_pureQmlTypes[inlineComponent].size(); + } + + // add explicit components to the object creation indices + { + QHash<InlineComponentOrDocumentRootName, qsizetype> index; + for (const QQmlJSScope::ConstPtr &c : std::as_const(explicitComponents)) { + const InlineComponentOrDocumentRootName inlineComponent = + c->enclosingInlineComponentName(); + m_creationIndices[c] = + m_pureQmlTypes[inlineComponent].size() + index[inlineComponent]++; + m_inlineComponentTypeCount[inlineComponent]++; + } + } + + // m_qmlTypesWithQmlBases should contain the types to be compiled. + // Some types should not be compiled and are therefore filtered out: + // * deferred types + // * types inside of capital-c-Components (implicit and explicit) + // * non-composite types (that is, types not defined in qml) + // + // This can not be done earlier as implicitly wrapped Components are + // only known after visitation is over! + + // filter step: + for (const auto &inlineComponent : m_inlineComponentNames) { + QList<QQmlJSScope::ConstPtr> filteredQmlTypesWithQmlBases; + QList<QQmlJSScope::ConstPtr> &unfilteredQmlTypesWithQmlBases = + m_qmlTypesWithQmlBases[inlineComponent]; + filteredQmlTypesWithQmlBases.reserve(unfilteredQmlTypesWithQmlBases.size()); + std::copy_if(unfilteredQmlTypesWithQmlBases.cbegin(), unfilteredQmlTypesWithQmlBases.cend(), + std::back_inserter(filteredQmlTypesWithQmlBases), + [&](const QQmlJSScope::ConstPtr &type) { + auto base = type->baseType(); + return base && base->isComposite() && !isOrUnderComponent(type) + && !isOrUnderDeferred(type); + }); + qSwap(unfilteredQmlTypesWithQmlBases, filteredQmlTypesWithQmlBases); + } + + // count QmlIR::Objects in the document - the amount is used to calculate + // object indices of implicit components + QHash<InlineComponentOrDocumentRootName, qsizetype> qmlScopeCount; + const auto countQmlScopes = [&](const QQmlJSScope::ConstPtr &scope) { + if (scope->isArrayScope()) // special kind of QQmlJSScope::QMLScope + return; + switch (scope->scopeType()) { + case QQmlSA::ScopeType::QMLScope: + case QQmlSA::ScopeType::GroupedPropertyScope: + case QQmlSA::ScopeType::AttachedPropertyScope: { + ++qmlScopeCount[scope->enclosingInlineComponentName()]; + break; + } + default: + return; + } + return; + }; + // order doesn't matter (so re-use QQmlJSUtils) + QQmlJSUtils::traverseFollowingQmlIrObjectStructure(m_exportedRootScope, countQmlScopes); + + // figure synthetic indices of QQmlComponent-wrapped types + int syntheticCreationIndex; + const auto addSyntheticIndex = [&](const QQmlJSScope::ConstPtr &type) { + // explicit component + if (isExplicitComponent(type)) { + m_syntheticTypeIndices[type] = m_qmlIrObjectIndices.value(type, -1); + return true; + } + // implicit component + if (isImplicitComponent(type)) { + const int index = + qmlScopeCount[type->enclosingInlineComponentName()] + syntheticCreationIndex++; + m_syntheticTypeIndices[type] = index; + return true; + } + return false; + }; + + for (const auto &inlineComponentName : m_inlineComponentNames) { + syntheticCreationIndex = 0; // reset for each inline component + iterateTypes(m_inlineComponents[inlineComponentName], qmlIrOrderedBindings, + addSyntheticIndex); + } + + // figure runtime object ids for non-component wrapped types + int currentId; + const auto setRuntimeId = [&](const QQmlJSScope::ConstPtr &type) { + // any type wrapped in an implicit component shouldn't be processed + // here. even if it has id, it doesn't need to be set by qmltc + if (type->isComponentRootElement()) { + return true; + } + + if (m_typesWithId.contains(type)) { + m_typesWithId[type] = currentId++; + } + + return false; + }; + + for (const auto &inlineComponentName : m_inlineComponentNames) { + currentId = 0; // reset for each inline component + iterateTypes(m_inlineComponents[inlineComponentName], qmlIrOrderedBindings, setRuntimeId); + } +} + +static void setAliasData(QQmlJSMetaProperty *alias, const QQmlJSUtils::ResolvedAlias &origin) +{ + Q_ASSERT(origin.kind != QQmlJSUtils::AliasTarget_Invalid); + QmltcPropertyData compiledData(*alias); + if (alias->read().isEmpty()) + alias->setRead(compiledData.read); + if (origin.kind == QQmlJSUtils::AliasTarget_Object) // id-pointing aliases only have READ method + return; + if (origin.property.isWritable() && alias->write().isEmpty()) + alias->setWrite(compiledData.write); + + // the engine always compiles a notify for properties/aliases defined in qml code + // Yes, this generated notify will never be emitted. + if (alias->notify().isEmpty()) + alias->setNotify(compiledData.notify); + + if (!origin.property.bindable().isEmpty() && alias->bindable().isEmpty()) + alias->setBindable(compiledData.bindable); +} + +void QmltcVisitor::setupAliases() +{ + QStack<QQmlJSScope::Ptr> types; + types.push(m_exportedRootScope); + + while (!types.isEmpty()) { + QQmlJSScope::Ptr current = types.pop(); + auto properties = current->ownProperties(); + + for (QQmlJSMetaProperty &p : properties) { + if (!p.isAlias()) + continue; + + auto result = QQmlJSUtils::resolveAlias(m_scopesById, p, current, + QQmlJSUtils::AliasResolutionVisitor {}); + if (result.kind == QQmlJSUtils::AliasTarget_Invalid) { + m_logger->log(QStringLiteral("Cannot resolve alias \"%1\"").arg(p.propertyName()), + qmlUnresolvedAlias, current->sourceLocation()); + continue; + } + setAliasData(&p, result); + current->addOwnProperty(p); + } + } +} + +void QmltcVisitor::checkNamesAndTypes(const QQmlJSScope::ConstPtr &type) +{ + static const QString cppKeywords[] = { + u"alignas"_s, + u"alignof"_s, + u"and"_s, + u"and_eq"_s, + u"asm"_s, + u"atomic_cancel"_s, + u"atomic_commit"_s, + u"atomic_noexcept"_s, + u"auto"_s, + u"bitand"_s, + u"bitor"_s, + u"bool"_s, + u"break"_s, + u"case"_s, + u"catch"_s, + u"char"_s, + u"char16_t"_s, + u"char32_t"_s, + u"char8_t"_s, + u"class"_s, + u"co_await"_s, + u"co_return"_s, + u"co_yield"_s, + u"compl"_s, + u"concept"_s, + u"const"_s, + u"const_cast"_s, + u"consteval"_s, + u"constexpr"_s, + u"continue"_s, + u"decltype"_s, + u"default"_s, + u"delete"_s, + u"do"_s, + u"double"_s, + u"dynamic_cast"_s, + u"else"_s, + u"enum"_s, + u"explicit"_s, + u"export"_s, + u"extern"_s, + u"false"_s, + u"float"_s, + u"for"_s, + u"friend"_s, + u"goto"_s, + u"if"_s, + u"inline"_s, + u"int"_s, + u"long"_s, + u"mutable"_s, + u"namespace"_s, + u"new"_s, + u"noexcept"_s, + u"not"_s, + u"not_eq"_s, + u"nullptr"_s, + u"operator"_s, + u"or"_s, + u"or_eq"_s, + u"private"_s, + u"protected"_s, + u"public"_s, + u"reflexpr"_s, + u"register"_s, + u"reinterpret_cast"_s, + u"requires"_s, + u"return"_s, + u"short"_s, + u"signed"_s, + u"sizeof"_s, + u"static"_s, + u"static_assert"_s, + u"static_cast"_s, + u"struct"_s, + u"switch"_s, + u"synchronized"_s, + u"template"_s, + u"this"_s, + u"thread_local"_s, + u"throw"_s, + u"true"_s, + u"try"_s, + u"typedef"_s, + u"typeid"_s, + u"typename"_s, + u"union"_s, + u"unsigned"_s, + u"using"_s, + u"virtual"_s, + u"void"_s, + u"volatile"_s, + u"wchar_t"_s, + u"while"_s, + u"xor"_s, + u"xor_eq"_s, + }; + Q_ASSERT(std::is_sorted(std::begin(cppKeywords), std::end(cppKeywords))); + + const auto isReserved = [&](QStringView word) { + if (word.startsWith(QChar(u'_')) && word.size() >= 2 + && (word[1].isUpper() || word[1] == QChar(u'_'))) { + return true; // Identifiers starting with underscore and uppercase are reserved in C++ + } + return std::binary_search(std::begin(cppKeywords), std::end(cppKeywords), word); + }; + + const auto validate = [&](QStringView name, QStringView errorPrefix) { + if (!isReserved(name)) + return; + m_logger->log(errorPrefix + u" '" + name + u"' is a reserved C++ word, consider renaming", + qmlCompiler, type->sourceLocation()); + }; + + const auto validateType = [&type, this](const QQmlJSScope::ConstPtr &typeToCheck, + QStringView name, QStringView errorPrefix) { + if (type->moduleName().isEmpty() || typeToCheck.isNull()) + return; + + if (typeToCheck->isComposite() && typeToCheck->moduleName() != type->moduleName()) { + m_logger->log( + QStringLiteral( + "Can't compile the %1 type \"%2\" to C++ because it " + "lives in \"%3\" instead of the current file's \"%4\" QML module.") + .arg(errorPrefix, name, typeToCheck->moduleName(), type->moduleName()), + qmlCompiler, type->sourceLocation()); + } + }; + + validateType(type->baseType(), type->baseTypeName(), u"QML base"); + + const auto enums = type->ownEnumerations(); + for (auto it = enums.cbegin(); it != enums.cend(); ++it) { + const QQmlJSMetaEnum e = it.value(); + validate(e.name(), u"Enumeration"); + + const auto enumKeys = e.keys(); + for (const auto &key : enumKeys) + validate(key, u"Enumeration '%1' key"_s.arg(e.name())); + } + + const auto properties = type->ownProperties(); + for (auto it = properties.cbegin(); it != properties.cend(); ++it) { + const QQmlJSMetaProperty &p = it.value(); + validate(p.propertyName(), u"Property"); + + if (!p.isAlias() && !p.typeName().isEmpty()) + validateType(p.type(), p.typeName(), u"QML property"); + } + + const auto methods = type->ownMethods(); + for (auto it = methods.cbegin(); it != methods.cend(); ++it) { + const QQmlJSMetaMethod &m = it.value(); + validate(m.methodName(), u"Method"); + if (!m.returnTypeName().isEmpty()) + validateType(m.returnType(), m.returnTypeName(), u"QML method return"); + + for (const auto ¶meter : m.parameters()) { + validate(parameter.name(), u"Method '%1' parameter"_s.arg(m.methodName())); + if (!parameter.typeName().isEmpty()) + validateType(parameter.type(), parameter.typeName(), u"QML parameter"); + } + } + + // TODO: one could also test signal handlers' parameters but we do not store + // this information in QQmlJSMetaPropertyBinding currently +} + +/*! \internal + * Sets the file paths for the document and the inline components roots. + */ +void QmltcVisitor::setRootFilePath() +{ + const QString filePath = m_currentScope->filePath(); + if (filePath.endsWith(u".h")) // assume the correct path is set + return; + Q_ASSERT(filePath.endsWith(u".qml"_s)); + + const QString correctedFilePath = sourceDirectoryPath(filePath); + const QStringList paths = m_importer->resourceFileMapper()->resourcePaths( + QQmlJSResourceFileMapper::localFileFilter(correctedFilePath)); + auto firstHeader = std::find_if(paths.cbegin(), paths.cend(), + [](const QString &x) { return x.endsWith(u".h"_s); }); + if (firstHeader == paths.cend()) { + const QString matchedPaths = paths.isEmpty() ? u"<none>"_s : paths.join(u", "); + qCDebug(lcQmltcCompiler, + "Failed to find a header file name for path %s. Paths checked:\n%s", + correctedFilePath.toUtf8().constData(), matchedPaths.toUtf8().constData()); + return; + } + // NB: get the file name to avoid prefixes + m_currentScope->setFilePath(QFileInfo(*firstHeader).fileName()); +} + +QString QmltcVisitor::sourceDirectoryPath(const QString &path) +{ + auto result = QQmlJSUtils::sourceDirectoryPath(m_importer, path); + if (const QString *srcDirPath = std::get_if<QString>(&result)) + return *srcDirPath; + + const QQmlJS::DiagnosticMessage *error = std::get_if<QQmlJS::DiagnosticMessage>(&result); + Q_ASSERT(error); + qCDebug(lcQmltcCompiler, "%s", error->message.toUtf8().constData()); + // return input as a fallback + return path; +} + +QT_END_NAMESPACE diff --git a/tools/qmltc/qmltcvisitor.h b/tools/qmltc/qmltcvisitor.h new file mode 100644 index 0000000000..111df0e885 --- /dev/null +++ b/tools/qmltc/qmltcvisitor.h @@ -0,0 +1,191 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef QMLTCVISITOR_H +#define QMLTCVISITOR_H + +#include <QtCore/qstring.h> +#include <QtCore/qstringlist.h> +#include <QtCore/qlist.h> + +#include <QtQml/private/qqmlirbuilder_p.h> +#include <private/qqmljsimportvisitor_p.h> +#include <private/qqmljslogger_p.h> + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +class QmltcVisitor : public QQmlJSImportVisitor +{ + void findCppIncludes(); + void postVisitResolve(const QHash<QQmlJSScope::ConstPtr, QList<QQmlJSMetaPropertyBinding>> + &qmlIrOrderedBindings); + void setupAliases(); + void checkNamesAndTypes(const QQmlJSScope::ConstPtr &type); + void setRootFilePath(); + + QString sourceDirectoryPath(const QString &path); + + using InlineComponentOrDocumentRootName = QQmlJSScope::InlineComponentOrDocumentRootName; + +public: + QmltcVisitor(const QQmlJSScope::Ptr &target, QQmlJSImporter *importer, QQmlJSLogger *logger, + const QString &implicitImportDirectory, + const QStringList &qmldirFiles = QStringList()); + + bool visit(QQmlJS::AST::UiObjectDefinition *) override; + void endVisit(QQmlJS::AST::UiObjectDefinition *) override; + + bool visit(QQmlJS::AST::UiObjectBinding *) override; + void endVisit(QQmlJS::AST::UiObjectBinding *) override; + + bool visit(QQmlJS::AST::UiScriptBinding *) override; + + bool visit(QQmlJS::AST::UiPublicMember *) override; + + bool visit(QQmlJS::AST::UiInlineComponent *) override; + + void endVisit(QQmlJS::AST::UiProgram *) override; + + QList<QQmlJSScope::ConstPtr> + qmlTypesWithQmlBases(const InlineComponentOrDocumentRootName &inlinedComponentName) const + { + return m_qmlTypesWithQmlBases.value(inlinedComponentName); + } + QSet<QString> cppIncludeFiles() const { return m_cppIncludes; } + + qsizetype creationIndex(const QQmlJSScope::ConstPtr &type) const + { + Q_ASSERT(type->scopeType() == QQmlSA::ScopeType::QMLScope); + return m_creationIndices.value(type, -1); + } + + qsizetype typeCount(const InlineComponentOrDocumentRootName &inlineComponent) const + { + return m_inlineComponentTypeCount.value(inlineComponent); + } + + qsizetype qmlComponentIndex(const QQmlJSScope::ConstPtr &type) const + { + Q_ASSERT(type->scopeType() == QQmlSA::ScopeType::QMLScope); + return m_syntheticTypeIndices.value(type, -1); + } + + qsizetype qmlIrObjectIndex(const QQmlJSScope::ConstPtr &type) const + { + Q_ASSERT(type->scopeType() == QQmlSA::ScopeType::QMLScope); + Q_ASSERT(m_qmlIrObjectIndices.contains(type)); + return m_qmlIrObjectIndices.value(type, -1); + } + + /*! \internal + Returns a runtime index counterpart of `id: foo` for \a type. Returns -1 + if \a type does not have an id. + */ + int runtimeId(const QQmlJSScope::ConstPtr &type) const + { + // NB: this function is expected to be called for "pure" types + Q_ASSERT(!m_typesWithId.contains(type) || m_typesWithId[type] != -1); + return m_typesWithId.value(type, -1); + } + + /*! \internal + Returns all encountered QML types. + */ + QList<QQmlJSScope::ConstPtr> allQmlTypes() const { return qmlTypes(); } + + /*! \internal + Returns QML types which return \c false in + \c{isComponentRootElement()}. The QHash key are the enclosing inline component + or the root document name when not beloning to any inline component. + Called "pure", because these are the ones + that are not wrapped into QQmlComponent. Pure QML types can be created + through direct constructor invocation. + */ + QList<QQmlJSScope::ConstPtr> + pureQmlTypes(const InlineComponentOrDocumentRootName &inlineComponent) const + { + return m_pureQmlTypes[inlineComponent]; + } + + /*! + * \internal + * Returns a list of the inline components. This list ends with the document root. + */ + QList<InlineComponentOrDocumentRootName> inlineComponentNames() const + { + return m_inlineComponentNames; + } + QQmlJSScope::ConstPtr + inlineComponent(const InlineComponentOrDocumentRootName &inlineComponentName) const + { + return m_inlineComponents.value(inlineComponentName); + } + + /*! \internal + Returns \c true when \a type has deferred bindings. Returns \c false + otherwise. + */ + bool hasDeferredBindings(const QQmlJSScope::ConstPtr &type) const + { + return m_typesWithDeferredBindings.contains(type); + } + + enum Mode { Import, Compile }; + void setMode(Mode mode) { m_mode = mode; } + +protected: + QStringList m_qmlTypeNames; // names of QML types arranged as a stack + QHash<QString, int> m_qmlTypeNameCounts; + /*! + * \internal + * QML types with composite/QML base types, mapped from inline component name to types + */ + QHash<InlineComponentOrDocumentRootName, QList<QQmlJSScope::ConstPtr>> m_qmlTypesWithQmlBases; + QSet<QString> m_cppIncludes; // all C++ includes found from QQmlJSScope hierarchy + QHash<InlineComponentOrDocumentRootName, QList<QQmlJSScope::ConstPtr>> + m_pureQmlTypes; // the ones not under QQmlComponent + /*! + * \internal + * List of the names of the inline components, useful when iterating over QHash that + * uses those names as keys. Ends with the the document root. + */ + QList<InlineComponentOrDocumentRootName> m_inlineComponentNames; + /*! + * \internal + * Map inline component names to the corresponding type, and the document root + * name to all types not belonging to any inline component. + */ + QHash<InlineComponentOrDocumentRootName, QQmlJSScope::Ptr> m_inlineComponents; + /*! + * \internal + * Map types to their creation indices. Childrens are stored at their creation index in + * a QObject* array either in the document root or in the inline component they belong to. + * Therefore two types in the same file might have the same creation index, if they belong + * to different inline components. + */ + QHash<QQmlJSScope::ConstPtr, qsizetype> m_creationIndices; + /*! + * \internal + * Counts the types (pure qml types and explicit/implicit components) per inline component. + * Needed to set the size of the QObject* array in the document root or the inline component + * they belong to. + */ + QHash<InlineComponentOrDocumentRootName, qsizetype> m_inlineComponentTypeCount; + QHash<QQmlJSScope::ConstPtr, qsizetype> m_syntheticTypeIndices; + QHash<QQmlJSScope::ConstPtr, qsizetype> m_qmlIrObjectIndices; + + QSet<QQmlJSScope::ConstPtr> m_typesWithDeferredBindings; + + // prefer allQmlTypes or pureQmlTypes. this function is misleading in qmltc + QList<QQmlJSScope::ConstPtr> qmlTypes() const { return QQmlJSImportVisitor::qmlTypes(); } + + QHash<QQmlJSScope::ConstPtr, int> m_typesWithId; + + Mode m_mode = Mode::Import; +}; + +QT_END_NAMESPACE + +#endif // QMLTCVISITOR_H diff --git a/tools/qmltestrunner/.prev_CMakeLists.txt b/tools/qmltestrunner/.prev_CMakeLists.txt deleted file mode 100644 index 9d55cb5075..0000000000 --- a/tools/qmltestrunner/.prev_CMakeLists.txt +++ /dev/null @@ -1,19 +0,0 @@ -# Generated from qmltestrunner.pro. - -##################################################################### -## qmltestrunner Tool: -##################################################################### - -qt_get_tool_target_name(target_name qmltestrunner) -qt_internal_add_tool(${target_name} - TARGET_DESCRIPTION "QML Test Runner" - SOURCES - main.cpp - PUBLIC_LIBRARIES - Qt::Gui - Qt::Qml - Qt::QuickTest -) - -#### Keys ignored in scope 1:.:.:qmltestrunner.pro:<TRUE>: -# QMAKE_TARGET_DESCRIPTION = "QML" "Test" "Runner" diff --git a/tools/qmltestrunner/CMakeLists.txt b/tools/qmltestrunner/CMakeLists.txt index ebdec60bb5..0823ca0f6d 100644 --- a/tools/qmltestrunner/CMakeLists.txt +++ b/tools/qmltestrunner/CMakeLists.txt @@ -1,3 +1,6 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + # Generated from qmltestrunner.pro. ##################################################################### @@ -10,11 +13,12 @@ qt_internal_add_tool(${target_name} TOOLS_TARGET Qml # special case SOURCES main.cpp - PUBLIC_LIBRARIES + LIBRARIES Qt::Gui Qt::Qml Qt::QuickTest ) +qt_internal_return_unless_building_tools() #### Keys ignored in scope 1:.:.:qmltestrunner.pro:<TRUE>: # QMAKE_TARGET_DESCRIPTION = "QML" "Test" "Runner" diff --git a/tools/qmltestrunner/main.cpp b/tools/qmltestrunner/main.cpp index 2463757282..d4d05a2464 100644 --- a/tools/qmltestrunner/main.cpp +++ b/tools/qmltestrunner/main.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the test suite 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$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include <QtQuickTest/quicktest.h> #include <QtCore/qstring.h> diff --git a/tools/qmltime/.prev_CMakeLists.txt b/tools/qmltime/.prev_CMakeLists.txt deleted file mode 100644 index 71a1d76666..0000000000 --- a/tools/qmltime/.prev_CMakeLists.txt +++ /dev/null @@ -1,32 +0,0 @@ -# Generated from qmltime.pro. - -##################################################################### -## qmltime Tool: -##################################################################### - -qt_get_tool_target_name(target_name qmltime) -qt_internal_add_tool(${target_name} - TARGET_DESCRIPTION "QML Time" - SOURCES - qmltime.cpp qmltime.h - PUBLIC_LIBRARIES - Qt::Gui - Qt::Qml - Qt::Quick - Qt::QuickPrivate -) - -#### Keys ignored in scope 1:.:.:qmltime.pro:<TRUE>: -# QMAKE_TARGET_DESCRIPTION = "QML" "Time" -# QML_IMPORT_NAME = "QmlTime" -# QML_IMPORT_VERSION = "1.0" - -## Scopes: -##################################################################### - -set_target_properties(${target_name} PROPERTIES - QT_QML_MODULE_VERSION 1.0 - QT_QML_MODULE_URI QmlTime -) - -qt6_qml_type_registration(${target_name}) diff --git a/tools/qmltime/CMakeLists.txt b/tools/qmltime/CMakeLists.txt index 1486885e33..e3c62e2cf9 100644 --- a/tools/qmltime/CMakeLists.txt +++ b/tools/qmltime/CMakeLists.txt @@ -1,3 +1,6 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + # Generated from qmltime.pro. ##################################################################### @@ -10,29 +13,17 @@ qt_internal_add_tool(${target_name} TOOLS_TARGET Qml # special case SOURCES qmltime.cpp qmltime.h - PUBLIC_LIBRARIES + LIBRARIES Qt::Gui Qt::Qml Qt::Quick Qt::QuickPrivate ) +qt_internal_return_unless_building_tools() -#### Keys ignored in scope 1:.:.:qmltime.pro:<TRUE>: -# QMAKE_TARGET_DESCRIPTION = "QML" "Time" -# QML_IMPORT_NAME = "QmlTime" -# QML_IMPORT_VERSION = "1.0" - -## Scopes: -##################################################################### - -# Don't set properties on a host tool when cross compiling, because it -# is not being built. -if(NOT CMAKE_CROSSCOMPILING OR QT_BUILD_TOOLS_WHEN_CROSSCOMPILING) - -set_target_properties(${target_name} PROPERTIES - QT_QML_MODULE_VERSION 1.0 - QT_QML_MODULE_URI QmlTime +# Turn the tool into its own self-contained qml module +qt_internal_add_qml_module(${target_name} + URI QmlTime + VERSION 1.0 + NO_PLUGIN ) -qt6_qml_type_registration(${target_name}) -endif() - diff --git a/tools/qmltime/example.qml b/tools/qmltime/example.qml index e717a074e1..5333c539c6 100644 --- a/tools/qmltime/example.qml +++ b/tools/qmltime/example.qml @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtQml module 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$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 import QtQuick 2.0 import QmlTime 1.0 as QmlTime diff --git a/tools/qmltime/linelaidout.qml b/tools/qmltime/linelaidout.qml index e8ac636ed6..e0a129e4da 100644 --- a/tools/qmltime/linelaidout.qml +++ b/tools/qmltime/linelaidout.qml @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtQml module 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$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 import QtQuick 2.0 import QmlTime 1.0 as QmlTime diff --git a/tools/qmltime/qmltime.cpp b/tools/qmltime/qmltime.cpp index ca10fdde53..d36d2953d1 100644 --- a/tools/qmltime/qmltime.cpp +++ b/tools/qmltime/qmltime.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the test suite 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$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "qmltime.h" diff --git a/tools/qmltime/qmltime.h b/tools/qmltime/qmltime.h index a23dc902e2..fa0f44a5e4 100644 --- a/tools/qmltime/qmltime.h +++ b/tools/qmltime/qmltime.h @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2020 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the test suite 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$ -** -****************************************************************************/ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #ifndef QMLTIME_H #define QMLTIME_H @@ -63,6 +38,5 @@ private: QQuickView m_view; QQuickItem *m_item; }; -QML_DECLARE_TYPE(Timer); #endif // QMLTIME_H diff --git a/tools/qmltime/tests/anchors/empty.qml b/tools/qmltime/tests/anchors/empty.qml index e6a24a83ca..b25ab5da67 100644 --- a/tools/qmltime/tests/anchors/empty.qml +++ b/tools/qmltime/tests/anchors/empty.qml @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtQml module 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$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 import QtQuick 2.0 import QmlTime 1.0 as QmlTime diff --git a/tools/qmltime/tests/anchors/fill.qml b/tools/qmltime/tests/anchors/fill.qml index 121eb404fc..bdc6db435f 100644 --- a/tools/qmltime/tests/anchors/fill.qml +++ b/tools/qmltime/tests/anchors/fill.qml @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtQml module 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$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 import QtQuick 2.0 import QmlTime 1.0 as QmlTime diff --git a/tools/qmltime/tests/anchors/null.qml b/tools/qmltime/tests/anchors/null.qml index 391aac89e3..02602d2c8c 100644 --- a/tools/qmltime/tests/anchors/null.qml +++ b/tools/qmltime/tests/anchors/null.qml @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtQml module 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$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 import QtQuick 2.0 import QmlTime 1.0 as QmlTime diff --git a/tools/qmltime/tests/animation/large.qml b/tools/qmltime/tests/animation/large.qml index 2f7e86cada..435a8fbe29 100644 --- a/tools/qmltime/tests/animation/large.qml +++ b/tools/qmltime/tests/animation/large.qml @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtQml module 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$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 import QtQuick 2.0 import QmlTime 1.0 as QmlTime diff --git a/tools/qmltime/tests/animation/largeNoProps.qml b/tools/qmltime/tests/animation/largeNoProps.qml index 504953b9e6..60baf7fb08 100644 --- a/tools/qmltime/tests/animation/largeNoProps.qml +++ b/tools/qmltime/tests/animation/largeNoProps.qml @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtQml module 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$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 import QtQuick 2.0 import QmlTime 1.0 as QmlTime diff --git a/tools/qmltime/tests/item_creation/children.qml b/tools/qmltime/tests/item_creation/children.qml index 73d9b6d364..ad7c271514 100644 --- a/tools/qmltime/tests/item_creation/children.qml +++ b/tools/qmltime/tests/item_creation/children.qml @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtQml module 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$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 import QtQuick 2.0 import QmlTime 1.0 as QmlTime diff --git a/tools/qmltime/tests/item_creation/data.qml b/tools/qmltime/tests/item_creation/data.qml index 40f2abfc7b..51f74ab1e0 100644 --- a/tools/qmltime/tests/item_creation/data.qml +++ b/tools/qmltime/tests/item_creation/data.qml @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtQml module 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$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 import QtQuick 2.0 import QmlTime 1.0 as QmlTime diff --git a/tools/qmltime/tests/item_creation/no_creation.qml b/tools/qmltime/tests/item_creation/no_creation.qml index 9149303137..7a15575392 100644 --- a/tools/qmltime/tests/item_creation/no_creation.qml +++ b/tools/qmltime/tests/item_creation/no_creation.qml @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtQml module 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$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 import QtQuick 2.0 import QmlTime 1.0 as QmlTime diff --git a/tools/qmltime/tests/item_creation/resources.qml b/tools/qmltime/tests/item_creation/resources.qml index 7afcacc892..6e920a8da4 100644 --- a/tools/qmltime/tests/item_creation/resources.qml +++ b/tools/qmltime/tests/item_creation/resources.qml @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtQml module 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$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 import QtQuick 2.0 import QmlTime 1.0 as QmlTime diff --git a/tools/qmltime/tests/loader/Loaded.qml b/tools/qmltime/tests/loader/Loaded.qml index 946933bafa..af41f4ef19 100644 --- a/tools/qmltime/tests/loader/Loaded.qml +++ b/tools/qmltime/tests/loader/Loaded.qml @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtQml module 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$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 import QtQuick 2.0 diff --git a/tools/qmltime/tests/loader/component_loader.qml b/tools/qmltime/tests/loader/component_loader.qml index 8b338f37be..e8323e6bc0 100644 --- a/tools/qmltime/tests/loader/component_loader.qml +++ b/tools/qmltime/tests/loader/component_loader.qml @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtQml module 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$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 import QtQuick 2.0 import QmlTime 1.0 as QmlTime diff --git a/tools/qmltime/tests/loader/empty_loader.qml b/tools/qmltime/tests/loader/empty_loader.qml index 356195a4a9..2bdaa2d37f 100644 --- a/tools/qmltime/tests/loader/empty_loader.qml +++ b/tools/qmltime/tests/loader/empty_loader.qml @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtQml module 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$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 import QtQuick 2.0 import QmlTime 1.0 as QmlTime diff --git a/tools/qmltime/tests/loader/no_loader.qml b/tools/qmltime/tests/loader/no_loader.qml index 4de2dc26f1..3c3a562a5b 100644 --- a/tools/qmltime/tests/loader/no_loader.qml +++ b/tools/qmltime/tests/loader/no_loader.qml @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtQml module 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$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 import QtQuick 2.0 import QmlTime 1.0 as QmlTime diff --git a/tools/qmltime/tests/loader/source_loader.qml b/tools/qmltime/tests/loader/source_loader.qml index 38b47a134d..e6b8991d1b 100644 --- a/tools/qmltime/tests/loader/source_loader.qml +++ b/tools/qmltime/tests/loader/source_loader.qml @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtQml module 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$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 import QtQuick 2.0 import QmlTime 1.0 as QmlTime diff --git a/tools/qmltime/tests/positioner_creation/no_positioner.qml b/tools/qmltime/tests/positioner_creation/no_positioner.qml index c473a89d38..3f81500908 100644 --- a/tools/qmltime/tests/positioner_creation/no_positioner.qml +++ b/tools/qmltime/tests/positioner_creation/no_positioner.qml @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtQml module 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$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 import QtQuick 2.0 import QmlTime 1.0 as QmlTime diff --git a/tools/qmltime/tests/positioner_creation/null_positioner.qml b/tools/qmltime/tests/positioner_creation/null_positioner.qml index e459c4060f..4741b62ebf 100644 --- a/tools/qmltime/tests/positioner_creation/null_positioner.qml +++ b/tools/qmltime/tests/positioner_creation/null_positioner.qml @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtQml module 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$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 import QtQuick 2.0 import QmlTime 1.0 as QmlTime diff --git a/tools/qmltime/tests/positioner_creation/positioner.qml b/tools/qmltime/tests/positioner_creation/positioner.qml index 569dc8187c..e62aa5f72b 100644 --- a/tools/qmltime/tests/positioner_creation/positioner.qml +++ b/tools/qmltime/tests/positioner_creation/positioner.qml @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtQml module 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$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 import QtQuick 2.0 import QmlTime 1.0 as QmlTime diff --git a/tools/qmltime/tests/vmemetaobject/null.qml b/tools/qmltime/tests/vmemetaobject/null.qml index 520ad8bc0e..bb634824c5 100644 --- a/tools/qmltime/tests/vmemetaobject/null.qml +++ b/tools/qmltime/tests/vmemetaobject/null.qml @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtQml module 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$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 import QtQuick 2.0 import QmlTime 1.0 as QmlTime diff --git a/tools/qmltime/tests/vmemetaobject/property.qml b/tools/qmltime/tests/vmemetaobject/property.qml index df31145e5f..f217585f7f 100644 --- a/tools/qmltime/tests/vmemetaobject/property.qml +++ b/tools/qmltime/tests/vmemetaobject/property.qml @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtQml module 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$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 import QtQuick 2.0 import QmlTime 1.0 as QmlTime diff --git a/tools/qmltime/textingrid.qml b/tools/qmltime/textingrid.qml index 41073f50dc..8b2491fb26 100644 --- a/tools/qmltime/textingrid.qml +++ b/tools/qmltime/textingrid.qml @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtQml module 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$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 import QtQuick 2.0 import QmlTime 1.0 as QmlTime diff --git a/tools/qmltyperegistrar/CMakeLists.txt b/tools/qmltyperegistrar/CMakeLists.txt new file mode 100644 index 0000000000..74b1134e64 --- /dev/null +++ b/tools/qmltyperegistrar/CMakeLists.txt @@ -0,0 +1,28 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +##################################################################### +## qmltyperegistrar Tool: +##################################################################### + +qt_get_tool_target_name(target_name qmltyperegistrar) +qt_internal_add_tool(${target_name} + TARGET_DESCRIPTION "QML Types Registrar" + TOOLS_TARGET Qml # special case + INSTALL_DIR "${INSTALL_LIBEXECDIR}" + SOURCES + qmltyperegistrar.cpp + DEFINES + QT_NO_CAST_FROM_ASCII + QT_NO_CAST_TO_ASCII + LIBRARIES + Qt::Core + Qt::QmlTypeRegistrarPrivate +) + +# support for .pro projects needing qmltyperegistrar +set(qmltyperegistrar_mkspecs "${CMAKE_CURRENT_SOURCE_DIR}/qmltypes.prf") +set(mkspecs_install_dir "${INSTALL_MKSPECSDIR}") +qt_path_join(mkspecs_install_dir "${QT_INSTALL_DIR}" "${mkspecs_install_dir}" "features") +qt_copy_or_install(FILES "${qmltyperegistrar_mkspecs}" + DESTINATION ${mkspecs_install_dir}) diff --git a/tools/qmltyperegistrar/qmltyperegistrar.cpp b/tools/qmltyperegistrar/qmltyperegistrar.cpp new file mode 100644 index 0000000000..248926fb33 --- /dev/null +++ b/tools/qmltyperegistrar/qmltyperegistrar.cpp @@ -0,0 +1,207 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include <QCoreApplication> +#include <QCommandLineParser> +#include <QDir> +#include <QFile> +#include <QScopedPointer> + +#include <cstdlib> + +#include <QtQmlTypeRegistrar/private/qqmltyperegistrar_p.h> +#include <QtQmlTypeRegistrar/private/qqmltyperegistrarutils_p.h> + +using namespace Qt::Literals; + +int main(int argc, char **argv) +{ + // Produce reliably the same output for the same input by disabling QHash's random seeding. + QHashSeed::setDeterministicGlobalSeed(); + + // No, you are not supposed to mess with the message pattern. + // Qt Creator wants to read those messages as-is and we want the convenience + // of QDebug to print them. + qputenv("QT_MESSAGE_PATTERN", "%{if-category}%{category}: %{endif}%{message}"); + + QCoreApplication app(argc, argv); + QCoreApplication::setApplicationName(QStringLiteral("qmltyperegistrar")); + QCoreApplication::setApplicationVersion(QLatin1String(QT_VERSION_STR)); + + QCommandLineParser parser; + parser.addHelpOption(); + parser.addVersionOption(); + + QCommandLineOption outputOption(QStringLiteral("o")); + outputOption.setDescription(QStringLiteral("Write output to specified file.")); + outputOption.setValueName(QStringLiteral("file")); + outputOption.setFlags(QCommandLineOption::ShortOptionStyle); + parser.addOption(outputOption); + + QCommandLineOption privateIncludesOption( + QStringLiteral("private-includes"), + QStringLiteral("Include headers ending in \"_p.h\" using \"#include <private/foo_p.h>\"" + "rather than \"#include <foo_p.h>\".")); + parser.addOption(privateIncludesOption); + + QCommandLineOption importNameOption(QStringLiteral("import-name")); + importNameOption.setDescription(QStringLiteral("Name of the module to use for type and module " + "registrations.")); + importNameOption.setValueName(QStringLiteral("module name")); + parser.addOption(importNameOption); + + QCommandLineOption pastMajorVersionOption(QStringLiteral("past-major-version")); + pastMajorVersionOption.setDescription( + QStringLiteral("Past major version to use for type and module " + "registrations.")); + pastMajorVersionOption.setValueName(QStringLiteral("past major version")); + parser.addOption(pastMajorVersionOption); + + QCommandLineOption majorVersionOption(QStringLiteral("major-version")); + majorVersionOption.setDescription(QStringLiteral("Major version to use for type and module " + "registrations.")); + majorVersionOption.setValueName(QStringLiteral("major version")); + parser.addOption(majorVersionOption); + + QCommandLineOption minorVersionOption(QStringLiteral("minor-version")); + minorVersionOption.setDescription(QStringLiteral("Minor version to use for module " + "registration.")); + minorVersionOption.setValueName(QStringLiteral("minor version")); + parser.addOption(minorVersionOption); + + QCommandLineOption namespaceOption(QStringLiteral("namespace")); + namespaceOption.setDescription(QStringLiteral("Generate type registration functions " + "into a C++ namespace.")); + namespaceOption.setValueName(QStringLiteral("namespace")); + parser.addOption(namespaceOption); + + QCommandLineOption pluginTypesOption(QStringLiteral("generate-qmltypes")); + pluginTypesOption.setDescription(QStringLiteral("Generate qmltypes into specified file.")); + pluginTypesOption.setValueName(QStringLiteral("qmltypes file")); + parser.addOption(pluginTypesOption); + + QCommandLineOption foreignTypesOption(QStringLiteral("foreign-types")); + foreignTypesOption.setDescription( + QStringLiteral("Comma separated list of other modules' metatypes files " + "to consult for foreign types when generating " + "qmltypes file.")); + foreignTypesOption.setValueName(QStringLiteral("foreign types")); + parser.addOption(foreignTypesOption); + + QCommandLineOption followForeignVersioningOption(QStringLiteral("follow-foreign-versioning")); + followForeignVersioningOption.setDescription( + QStringLiteral("If this option is set the versioning scheme of foreign base classes " + "will be respected instead of ignored. Mostly useful for modules who " + "want to follow Qt's versioning scheme.")); + parser.addOption(followForeignVersioningOption); + + QCommandLineOption jsroot(QStringLiteral("jsroot")); + jsroot.setDescription( + QStringLiteral("Use the JavaScript root object's meta types as sole input and do not " + "generate any C++ output. Only useful in combination with " + "--generate-qmltypes")); + parser.addOption(jsroot); + + QCommandLineOption extract(u"extract"_s); + extract.setDescription( + u"Extract QML types from a module and use QML_FOREIGN to register them"_s); + parser.addOption(extract); + + parser.addPositionalArgument(QStringLiteral("[MOC generated json file]"), + QStringLiteral("MOC generated json output.")); + + QStringList arguments; + if (!QmlTypeRegistrar::argumentsFromCommandLineAndFile(arguments, app.arguments())) + return EXIT_FAILURE; + + parser.process(arguments); + + const QString module = parser.value(importNameOption); + + const QLatin1String jsrootMetaTypes + = QLatin1String(":/qt-project.org/meta_types/jsroot_metatypes.json"); + QStringList files = parser.positionalArguments(); + if (parser.isSet(jsroot)) { + if (parser.isSet(extract)) { + error(module) << "If --jsroot is passed, no type registrations can be extracted."; + return EXIT_FAILURE; + } + if (parser.isSet(outputOption)) { + error(module) << "If --jsroot is passed, no C++ output can be generated."; + return EXIT_FAILURE; + } + if (!files.isEmpty() || parser.isSet(foreignTypesOption)) { + error(module) << "If --jsroot is passed, no further metatypes can be processed."; + return EXIT_FAILURE; + } + + files.append(jsrootMetaTypes); + } + + MetaTypesJsonProcessor processor(parser.isSet(privateIncludesOption)); + if (!processor.processTypes(files)) + return EXIT_FAILURE; + + processor.postProcessTypes(); + + if (!parser.isSet(jsroot)) { + processor.processForeignTypes(jsrootMetaTypes); + if (parser.isSet(foreignTypesOption)) + processor.processForeignTypes(parser.value(foreignTypesOption).split(QLatin1Char(','))); + } + + processor.postProcessForeignTypes(); + + if (parser.isSet(extract)) { + if (!parser.isSet(outputOption)) { + error(module) << "The output file name must be provided"; + return EXIT_FAILURE; + } + QString baseName = parser.value(outputOption); + return QmlTypeRegistrar::runExtract(baseName, processor); + } + + QmlTypeRegistrar typeRegistrar; + typeRegistrar.setIncludes(processor.includes()); + typeRegistrar.setModuleNameAndNamespace(module, parser.value(namespaceOption)); + QTypeRevision moduleVersion = QTypeRevision::fromVersion( + parser.value(majorVersionOption).toInt(), parser.value(minorVersionOption).toInt()); + QList<quint8> pastMajorVersions; + for (const auto &x : parser.values(pastMajorVersionOption)) + pastMajorVersions.append(x.toUInt()); + + typeRegistrar.setModuleVersions(moduleVersion, pastMajorVersions, + parser.isSet(followForeignVersioningOption)); + typeRegistrar.setTypes(processor.types(), processor.foreignTypes()); + + if (!parser.isSet(jsroot)) { + if (module.isEmpty()) { + warning(module) << "The module name is empty. Cannot generate C++ code"; + } else if (parser.isSet(outputOption)) { + // extract does its own file handling + QString outputName = parser.value(outputOption); + QFile file(outputName); + if (!file.open(QIODeviceBase::WriteOnly)) { + error(QDir::toNativeSeparators(outputName)) + << "Cannot open file for writing:" << file.errorString(); + return EXIT_FAILURE; + } + QTextStream output(&file); + typeRegistrar.write(output, outputName); + } else { + QTextStream output(stdout); + typeRegistrar.write(output, "stdout"); + } + } + + if (!parser.isSet(pluginTypesOption)) + return EXIT_SUCCESS; + + typeRegistrar.setReferencedTypes(processor.referencedTypes()); + const QString qmltypes = parser.value(pluginTypesOption); + if (!typeRegistrar.generatePluginTypes(qmltypes)) { + error(qmltypes) << "Cannot generate qmltypes file"; + return EXIT_FAILURE; + } + return EXIT_SUCCESS; +} diff --git a/tools/qmltyperegistrar/qmltypes.prf b/tools/qmltyperegistrar/qmltypes.prf new file mode 100644 index 0000000000..8011a758fb --- /dev/null +++ b/tools/qmltyperegistrar/qmltypes.prf @@ -0,0 +1,137 @@ +CONFIG += metatypes + +qtPrepareLibExecTool(QML_TYPEREGISTRAR, qmltyperegistrar) + +isEmpty(QML_IMPORT_VERSION): \ + QML_IMPORT_VERSION = $$IMPORT_VERSION + +# from moc.prf +isEmpty(QML_IMPORT_MAJOR_VERSION):!isEmpty(QML_IMPORT_VERSION): \ + QML_IMPORT_MAJOR_VERSION = $$section(QML_IMPORT_VERSION, ., 0, 0) + +isEmpty(QML_IMPORT_MINOR_VERSION):!isEmpty(QML_IMPORT_VERSION): \ + QML_IMPORT_MINOR_VERSION = $$section(QML_IMPORT_VERSION, ., 1, 1) + +isEmpty(QML_IMPORT_NAME):!isEmpty(TARGETPATH) { + QML_IMPORT_NAME = $$replace(TARGETPATH, "/", ".") + QML_IMPORT_NAME = $$replace(QML_IMPORT_NAME, .$${QML_IMPORT_MAJOR_VERSION}$, '') +} + +isEmpty(QML_IMPORT_NAME) { + error("Need TARGET_PATH or QML_IMPORT_NAME in order to generate qml types."); +} + +isEmpty(QML_IMPORT_MAJOR_VERSION) { + error("Need IMPORT_VERSION, QML_IMPORT_VERSION, or QML_IMPORT_MAJOR_VERSION in order to generate qml types."); +} + +isEmpty(QML_IMPORT_MINOR_VERSION) { + QML_IMPORT_MINOR_VERSION = 0 +} + +TARGET_BASENAME = $$lower($$basename(TARGET)) +TARGET_BASENAME ~= s/\s/_/g + +isEmpty(QMLTYPES_FILENAME) { + plugin: QMLTYPES_FILENAME = plugins.qmltypes + else: QMLTYPES_FILENAME = $${TARGET_BASENAME}.qmltypes +} + +qt_module_deps = $$replace(QT, -private$, '') +qt_module_deps += $$replace(QT_PRIVATE, -private$, '') +qt_module_deps = $$resolve_depends(qt_module_deps, "QT.", ".depends" ".run_depends") +qt_module_deps = $$replace(qt_module_deps, _private$, '') +qt_module_deps = $$unique(qt_module_deps) + +# We know we need to prefer the CMake debug build type for qmake debug builds. Which of the CMake +# release build types should be preferred for a qmake release build is guesswork. We apply a +# heuristic here: +# +# If you've gone to the trouble of building a "minsizerel" Qt, you probably want to use it for your +# qmake "release" builds. Conversely, if you have both a "release" and a "relwithdebinfo" Qt, you +# probably want to use the "release" Qt build for qmake "release" builds and the "relwithdebinfo" +# one for qmake "debug" builds. +# +# If no fitting build type exists, we accept the others. On linux, for example, we typically +# have only a single "relwithdebinfo" Qt build for both debug and release builds of user projects. +build_types = minsizerel release relwithdebinfo debug +CONFIG(debug, debug|release): build_types = $$reverse(build_types) + +for(dep, qt_module_deps) { + android:ABI = _$${ANDROID_TARGET_ARCH} + infixed_module_name = $$eval(QT.$${dep}.module) + + for(build_type, build_types) { + isEmpty(QT_LIBINFIX) { + metatypes_filename = $$lower($${infixed_module_name})$${ABI}_$${build_type}_metatypes.json + } else { + metatypes_filename = $$lower($$replace($${infixed_module_name}, $$QT_LIBINFIX, ''))$${ABI}_$${build_type}_metatypes.json + } + metatypes_filepath = $$[QT_INSTALL_ARCHDATA]/metatypes/$${metatypes_filename} + exists($${metatypes_filepath}) { + QML_FOREIGN_METATYPES += $${metatypes_filepath} + break() + } + } +} + + +QML_TYPEREGISTRAR_FLAGS = \ + --generate-qmltypes=$$QMLTYPES_FILENAME \ + --import-name=$$QML_IMPORT_NAME \ + --major-version=$$QML_IMPORT_MAJOR_VERSION \ + --minor-version=$$QML_IMPORT_MINOR_VERSION \ + --foreign-types=$$join(QML_FOREIGN_METATYPES, ',') + +!isEmpty(QML_PAST_MAJOR_VERSIONS) { + for(past_major_version,QML_PAST_MAJOR_VERSIONS): QML_TYPEREGISTRAR_FLAGS += --past-major-version $$past_major_version +} + +!isEmpty(MODULE_PRIVATE_INCLUDES): QML_TYPEREGISTRAR_FLAGS += --private-includes + +METATYPES_JSON = $${TARGET_BASENAME}_metatypes.json + +TYPEREGISTRATIONS = $${TARGET_BASENAME}_qmltyperegistrations$${first(QMAKE_EXT_CPP)} + +qmltyperegistrar_compiler.CONFIG += combine +qmltyperegistrar_compiler.commands = \ + $$QML_TYPEREGISTRAR $$QML_TYPEREGISTRAR_FLAGS -o ${QMAKE_FILE_OUT} ${QMAKE_FILE_IN} +qmltyperegistrar_compiler.input = METATYPES_JSON +qmltyperegistrar_compiler.output = $$TYPEREGISTRATIONS +qmltyperegistrar_compiler.variable_out = SOURCES +qmltyperegistrar_compiler.name = Automatic QML type registration +qmltyperegistrar_compiler.dependency_type = TYPE_C +QMAKE_EXTRA_COMPILERS += qmltyperegistrar_compiler + +!contains(TEMPLATE, "vc.*") { # work around QTBUG-91033 + # Create a fake extra compiler to announce that we generate $$QMLTYPES_FILENAME. + # This allows us to use $$QMLTYPES_FILENAME as input in other extra compilers. + qmltyperegistrar_qmltypes.input = METATYPES_JSON + qmltyperegistrar_qmltypes.depends = $$TYPEREGISTRATIONS + qmltyperegistrar_qmltypes.output = $$QMLTYPES_FILENAME + qmltyperegistrar_qmltypes.CONFIG = no_link + qmltyperegistrar_qmltypes.commands = $$escape_expand(\\n) # force creation of rule + QMAKE_EXTRA_COMPILERS += qmltyperegistrar_qmltypes +} + +install_qmltypes { + INSTALL_QML_FILES = false + + android { + build_pass { + isEmpty(ANDROID_ABIS): ANDROID_ABIS = $$ALL_ANDROID_ABIS + ABI = $$first(ANDROID_ABIS) + equals(ABI, $$QT_ARCH): INSTALL_QML_FILES = true + } + } else: !debug_and_release|!build_all|CONFIG(release, debug|release): INSTALL_QML_FILES = true + + equals(INSTALL_QML_FILES, true) { + isEmpty(QMLTYPES_INSTALL_DIR): \ + QMLTYPES_INSTALL_DIR = $$[QT_INSTALL_QML]/$$TARGETPATH + do_install_qmltypes.files = $$OUT_PWD/$$QMLTYPES_FILENAME + do_install_qmltypes.path = $$QMLTYPES_INSTALL_DIR + do_install_qmltypes.CONFIG += no_check_exist + prefix_build: INSTALLS += do_install_qmltypes + else: COPIES += do_install_qmltypes + } +} diff --git a/tools/svgtoqml/CMakeLists.txt b/tools/svgtoqml/CMakeLists.txt new file mode 100644 index 0000000000..fd6c91e7ea --- /dev/null +++ b/tools/svgtoqml/CMakeLists.txt @@ -0,0 +1,32 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +##################################################################### +## svgtoqml Tool: +##################################################################### + +qt_get_tool_target_name(target_name svgtoqml) +qt_internal_add_tool(${target_name} + TARGET_DESCRIPTION "SVG to QML Converter" + TOOLS_TARGET Quick + SOURCES + main.cpp + LIBRARIES + Qt::Core + Qt::Gui + Qt::Qml + Qt::Quick + Qt::QuickVectorImageGeneratorPrivate +) +qt_internal_return_unless_building_tools() + +set(resource_files + "main.qml" +) + +qt_internal_add_resource(${target_name} "qml" + PREFIX + "/" + FILES + ${resource_files} +) diff --git a/tools/svgtoqml/main.cpp b/tools/svgtoqml/main.cpp new file mode 100644 index 0000000000..8ae290db84 --- /dev/null +++ b/tools/svgtoqml/main.cpp @@ -0,0 +1,113 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include <QGuiApplication> +#include <QQmlApplicationEngine> +#include <QCommandLineParser> +#include <QFile> +#include <QQuickWindow> +#include <QQuickItem> +#include <QtQuickVectorImageGenerator/private/qquickitemgenerator_p.h> +#include <QtQuickVectorImageGenerator/private/qquickqmlgenerator_p.h> +#include <QtQuickVectorImageGenerator/private/qquickvectorimageglobal_p.h> + +#define ENABLE_GUI + +int main(int argc, char *argv[]) +{ +#ifdef ENABLE_GUI + QGuiApplication app(argc, argv); +#else + QCoreApplication app(argc, argv); +#endif + + QCommandLineParser parser; + parser.setApplicationDescription("SVG to QML converter [tech preview]"); + parser.addHelpOption(); + parser.addPositionalArgument("input", QCoreApplication::translate("main", "SVG file to read.")); + parser.addPositionalArgument("output", QCoreApplication::translate("main", "QML file to write.")); + + QCommandLineOption optimizeOption("optimize-paths", + QCoreApplication::translate("main", "Optimize paths for the curve renderer.")); + parser.addOption(optimizeOption); + + QCommandLineOption curveRendererOption("curve-renderer", + QCoreApplication::translate("main", "Use the curve renderer in generated QML.")); + parser.addOption(curveRendererOption); + + QCommandLineOption typeNameOption(QStringList() << "t" << "type-name", + QCoreApplication::translate("main", "Use <typename> for Shape."), + QCoreApplication::translate("main", "typename")); + parser.addOption(typeNameOption); + + QCommandLineOption copyrightOption("copyright-statement", + QCoreApplication::translate("main", "Add <string> as a comment at the start of the generated file."), + QCoreApplication::translate("main", "string")); + parser.addOption(copyrightOption); + + QCommandLineOption outlineModeOption("outline-stroke-mode", + QCoreApplication::translate("main", "Stroke the outside of the filled shape instead of " + "the original path. Also sets optimize-paths.")); + parser.addOption(outlineModeOption); + +#ifdef ENABLE_GUI + QCommandLineOption guiOption(QStringList() << "v" << "view", + QCoreApplication::translate("main", "Display the SVG in a window.")); + parser.addOption(guiOption); +#endif + parser.process(app); + const QStringList args = parser.positionalArguments(); + if (args.size() < 1) { + parser.showHelp(1); + } + + const QString inFileName = args.at(0); + + QString commentString = QLatin1String("Generated from SVG file %1").arg(inFileName); + + const auto outFileName = args.size() > 1 ? args.at(1) : QString{}; + const auto typeName = parser.value(typeNameOption); + auto copyrightString = parser.value(copyrightOption); + + if (!copyrightString.isEmpty()) { + copyrightString = copyrightString.replace("\\n", "\n"); + commentString = copyrightString + u"\n" + commentString; + } + + QQuickVectorImageGenerator::GeneratorFlags flags; + if (parser.isSet(curveRendererOption)) + flags |= QQuickVectorImageGenerator::GeneratorFlag::CurveRenderer; + if (parser.isSet(optimizeOption)) + flags |= QQuickVectorImageGenerator::GeneratorFlag::OptimizePaths; + if (parser.isSet(outlineModeOption)) + flags |= (QQuickVectorImageGenerator::GeneratorFlag::OutlineStrokeMode + | QQuickVectorImageGenerator::GeneratorFlag::OptimizePaths); + + QQuickQmlGenerator generator(inFileName, flags, outFileName); + generator.setShapeTypeName(typeName); + generator.setCommentString(commentString); + generator.generate(); + +#ifdef ENABLE_GUI + if (parser.isSet(guiOption)) { + app.setOrganizationName("QtProject"); + const QUrl url(QStringLiteral("qrc:/main.qml")); + QQmlApplicationEngine engine; + QObject::connect(&engine, &QQmlApplicationEngine::objectCreated, + &app, [&](QObject *obj, const QUrl &objUrl){ + if (!obj && url == objUrl) + QCoreApplication::exit(-1); + if (obj) { + auto *containerItem = obj->findChild<QQuickItem*>(QStringLiteral("svg_item")); + QQuickItemGenerator generator(inFileName, flags, containerItem); + generator.generate(); + } + }); + engine.load(url); + return app.exec(); + } +#else + return 0; +#endif + +} diff --git a/tools/svgtoqml/main.qml b/tools/svgtoqml/main.qml new file mode 100644 index 0000000000..9a8ceefe5d --- /dev/null +++ b/tools/svgtoqml/main.qml @@ -0,0 +1,21 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtQuick +import QtQuick.Shapes +import QtQuick.Controls +import QtQuick.Layouts + +Window { + id: mainWindow + width: 1280 + height: 960 + visible: true + color: "white" + + Item { + id: svg + objectName: "svg_item" + anchors.centerIn: parent + } +} |